diff --git a/.github/actions/build/action.yml b/.github/actions/build/action.yml
index bc1fb8562..819beb288 100644
--- a/.github/actions/build/action.yml
+++ b/.github/actions/build/action.yml
@@ -8,6 +8,9 @@ inputs:
project-id:
description: 'WalletConnect project id'
required: true
+ cache-key:
+ description: 'Cache key to use for caching'
+ required: true
runs:
using: "composite"
@@ -34,4 +37,4 @@ runs:
with:
path: |
products.tar
- key: ${{ runner.os }}-deriveddata-${{ github.event.pull_request.head.sha }}
\ No newline at end of file
+ key: ${{ runner.os }}-deriveddata-${{ inputs.cache-key }}-${{ github.event.pull_request.head.sha }}
\ No newline at end of file
diff --git a/.github/actions/run_tests_without_building/action.yml b/.github/actions/run_tests_without_building/action.yml
index 682a1b87d..f8d0a3743 100644
--- a/.github/actions/run_tests_without_building/action.yml
+++ b/.github/actions/run_tests_without_building/action.yml
@@ -24,6 +24,10 @@ inputs:
gm-dapp-project-secret:
description: 'GM DApp Project Secret'
required: false
+ js-client-api-host:
+ description: 'JS Client Api Host'
+ required: false
+
runs:
using: "composite"
@@ -51,7 +55,7 @@ runs:
- name: Run integration tests
if: inputs.type == 'integration-tests'
shell: bash
- run: make integration_tests RELAY_HOST=${{ inputs.relay-endpoint }} PROJECT_ID=${{ inputs.project-id }} CAST_HOST=${{ inputs.notify-endpoint }} GM_DAPP_PROJECT_ID=${{ inputs.gm-dapp-project-id }} GM_DAPP_PROJECT_SECRET=${{ inputs.gm-dapp-project-secret }}
+ run: make integration_tests RELAY_HOST=${{ inputs.relay-endpoint }} PROJECT_ID=${{ inputs.project-id }} CAST_HOST=${{ inputs.notify-endpoint }} GM_DAPP_PROJECT_ID=${{ inputs.gm-dapp-project-id }} GM_DAPP_PROJECT_SECRET=${{ inputs.gm-dapp-project-secret }} JS_CLIENT_API_HOST=${{ inputs.js-client-api-host }}
# Relay Integration tests
- name: Run Relay integration tests
@@ -71,6 +75,11 @@ runs:
shell: bash
run: make notify_tests RELAY_HOST=${{ inputs.relay-endpoint }} PROJECT_ID=${{ inputs.project-id }} CAST_HOST=${{ inputs.notify-endpoint }} GM_DAPP_PROJECT_ID=${{ inputs.gm-dapp-project-id }} GM_DAPP_PROJECT_SECRET=${{ inputs.gm-dapp-project-secret }}
+ - name: Run x-platform protocol tests
+ if: inputs.type == 'x-platform-protocol-tests'
+ shell: bash
+ run: make x_platform_protocol_tests RELAY_HOST=${{ inputs.relay-endpoint }} PROJECT_ID=${{ inputs.project-id }} JS_CLIENT_API_HOST=${{ inputs.js-client-api-host }}
+
# Slack notification for failing smoke and relay tests
- name: Slack Notification for Failure
if: failure() && (inputs.type == 'smoke-tests' || inputs.type == 'relay-tests')
diff --git a/.github/workflows/build_artifacts.yml b/.github/workflows/build_artifacts.yml
index cc8adb7a5..94bff827b 100644
--- a/.github/workflows/build_artifacts.yml
+++ b/.github/workflows/build_artifacts.yml
@@ -11,11 +11,13 @@ on:
description: 'WalletConnect project id'
required: true
push:
- branches: [ main ]
+ branches:
+ - main
jobs:
build:
- runs-on: macos-12
+ runs-on:
+ group: apple-silicon
timeout-minutes: 15
steps:
@@ -44,4 +46,15 @@ jobs:
- uses: actions/upload-artifact@v3
with:
name: main-derivedData
- path: products.tar
\ No newline at end of file
+ path: products.tar
+ if-no-files-found: error
+
+ # Slack notification for failing smoke and relay tests
+ - name: Slack Notification for Failure
+ if: failure()
+ uses: 8398a7/action-slack@v3
+ with:
+ status: ${{ job.status }}
+ text: CI pipeline for preparing arifacts failed to build main branch or failed to upload artifact. Check the logs for more details.
+ env:
+ SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
\ No newline at end of file
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 704d88a64..ef5f81953 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -22,7 +22,8 @@ jobs:
prepare:
needs: authorize
- runs-on: macos-12
+ runs-on:
+ group: apple-silicon
steps:
- uses: actions/checkout@v3
with:
@@ -31,10 +32,12 @@ jobs:
- uses: ./.github/actions/build
with:
project-id: ${{ secrets.PROJECT_ID }}
+ cache-key: ci
test:
needs: prepare
- runs-on: macos-12
+ runs-on:
+ group: apple-silicon
timeout-minutes: 15
strategy:
fail-fast: false
@@ -50,7 +53,7 @@ jobs:
with:
path: |
products.tar
- key: ${{ runner.os }}-deriveddata-${{ github.event.pull_request.head.sha }}
+ key: ${{ runner.os }}-deriveddata-ci-${{ github.event.pull_request.head.sha }}
- name: Untar DerivedDataCache
shell: bash
@@ -66,7 +69,7 @@ jobs:
- name: Run integration tests
if: matrix.type == 'integration-tests'
shell: bash
- run: make integration_tests RELAY_HOST=relay.walletconnect.com PROJECT_ID=${{ secrets.PROJECT_ID }} CAST_HOST=cast.walletconnect.com GM_DAPP_PROJECT_ID=${{ secrets.GM_DAPP_PROJECT_ID }} GM_DAPP_PROJECT_SECRET=${{ secrets.GM_DAPP_PROJECT_SECRET }}
+ run: make integration_tests RELAY_HOST=relay.walletconnect.com PROJECT_ID=${{ secrets.PROJECT_ID }} CAST_HOST=notify.walletconnect.com GM_DAPP_PROJECT_ID=${{ secrets.GM_DAPP_PROJECT_ID }} GM_DAPP_PROJECT_SECRET=${{ secrets.GM_DAPP_PROJECT_SECRET }} JS_CLIENT_API_HOST=test-automation-api.walletconnect.com
# Relay Integration tests
- name: Run Relay integration tests
diff --git a/.github/workflows/deploy_pages.yml b/.github/workflows/deploy_pages.yml
index 55c70520a..9901d0c0d 100644
--- a/.github/workflows/deploy_pages.yml
+++ b/.github/workflows/deploy_pages.yml
@@ -27,7 +27,8 @@ jobs:
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
- runs-on: macos-12
+ runs-on:
+ group: apple-silicon
steps:
- name: Checkout
uses: actions/checkout@v3
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index 9a58e1a2f..3174bdc93 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -8,12 +8,12 @@ on:
workflow_dispatch:
jobs:
- build:
+ build:
runs-on: macos-12
steps:
- uses: actions/checkout@v3
-
+
- uses: actions/cache@v3
with:
path: |
@@ -26,12 +26,13 @@ jobs:
- name: Release
shell: bash
- env:
+ env:
MATCH_PASSWORD: ${{ secrets.MATCH_PASSWORD }}
GH_TOKEN: ${{ secrets.GH_TOKEN }}
GH_USER: ${{ secrets.GH_USER }}
APPLE_ISSUER_ID: ${{ secrets.APPLE_ISSUER_ID }}
APPLE_KEY_ID: ${{ secrets.APPLE_KEY_ID }}
APPLE_KEY_CONTENT: ${{ secrets.APPLE_KEY_CONTENT }}
+ WALLETAPP_SENTRY_DSN: ${{ secrets.WALLETAPP_SENTRY_DSN }}
run: |
- make release_all APPLE_ID=${{ secrets.APPLE_ID }} TOKEN=$(echo -n $GH_USER:$GH_TOKEN | base64) PROJECT_ID=${{ secrets.RELEASE_PROJECT_ID }}
+ make release_all APPLE_ID=${{ secrets.APPLE_ID }} TOKEN=$(echo -n $GH_USER:$GH_TOKEN | base64) PROJECT_ID=${{ secrets.RELEASE_PROJECT_ID }} WALLETAPP_SENTRY_DSN=${{ secrets.WALLETAPP_SENTRY_DSN }} MIXPANEL_TOKEN=${{secrets.MIXPANEL_TOKEN}}
diff --git a/.swiftpm/xcode/xcshareddata/xcschemes/NotifyTests.xcscheme b/.swiftpm/xcode/xcshareddata/xcschemes/NotifyTests.xcscheme
new file mode 100644
index 000000000..b94c9e014
--- /dev/null
+++ b/.swiftpm/xcode/xcshareddata/xcschemes/NotifyTests.xcscheme
@@ -0,0 +1,53 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/.swiftpm/xcode/xcshareddata/xcschemes/WalletConnect-Package.xcscheme b/.swiftpm/xcode/xcshareddata/xcschemes/WalletConnect-Package.xcscheme
index bcf1ba2ab..14b8eeb45 100644
--- a/.swiftpm/xcode/xcshareddata/xcschemes/WalletConnect-Package.xcscheme
+++ b/.swiftpm/xcode/xcshareddata/xcschemes/WalletConnect-Package.xcscheme
@@ -488,8 +488,13 @@
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
- shouldUseLaunchSchemeArgsEnv = "YES"
- shouldAutocreateTestPlan = "YES">
+ shouldUseLaunchSchemeArgsEnv = "YES">
+
+
+
+
diff --git a/Example/ExampleApp.xcodeproj/xcshareddata/xcschemes/WalletConnectPush.xcscheme b/.swiftpm/xcode/xcshareddata/xcschemes/WalletConnectNotify.xcscheme
similarity index 55%
rename from Example/ExampleApp.xcodeproj/xcshareddata/xcschemes/WalletConnectPush.xcscheme
rename to .swiftpm/xcode/xcshareddata/xcschemes/WalletConnectNotify.xcscheme
index c779cb9d1..9b01e10d1 100644
--- a/Example/ExampleApp.xcodeproj/xcshareddata/xcschemes/WalletConnectPush.xcscheme
+++ b/.swiftpm/xcode/xcshareddata/xcschemes/WalletConnectNotify.xcscheme
@@ -1,7 +1,7 @@
+ LastUpgradeVersion = "1430"
+ version = "1.7">
@@ -14,24 +14,10 @@
buildForAnalyzing = "YES">
-
-
-
-
+ BlueprintIdentifier = "WalletConnectNotify"
+ BuildableName = "WalletConnectNotify"
+ BlueprintName = "WalletConnectNotify"
+ ReferencedContainer = "container:">
@@ -40,19 +26,8 @@
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
- shouldUseLaunchSchemeArgsEnv = "YES">
-
-
-
-
-
-
+ shouldUseLaunchSchemeArgsEnv = "YES"
+ shouldAutocreateTestPlan = "YES">
+ BlueprintIdentifier = "WalletConnectNotify"
+ BuildableName = "WalletConnectNotify"
+ BlueprintName = "WalletConnectNotify"
+ ReferencedContainer = "container:">
diff --git a/Example/ExampleApp.xcodeproj/xcshareddata/xcschemes/WalletConnectEcho.xcscheme b/.swiftpm/xcode/xcshareddata/xcschemes/WalletConnectPush.xcscheme
similarity index 76%
rename from Example/ExampleApp.xcodeproj/xcshareddata/xcschemes/WalletConnectEcho.xcscheme
rename to .swiftpm/xcode/xcshareddata/xcschemes/WalletConnectPush.xcscheme
index 341890963..43b6f9bc1 100644
--- a/Example/ExampleApp.xcodeproj/xcshareddata/xcschemes/WalletConnectEcho.xcscheme
+++ b/.swiftpm/xcode/xcshareddata/xcschemes/WalletConnectPush.xcscheme
@@ -1,7 +1,7 @@
+ LastUpgradeVersion = "1430"
+ version = "1.7">
@@ -14,10 +14,10 @@
buildForAnalyzing = "YES">
+ BlueprintIdentifier = "WalletConnectPush"
+ BuildableName = "WalletConnectPush"
+ BlueprintName = "WalletConnectPush"
+ ReferencedContainer = "container:">
@@ -26,9 +26,8 @@
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
- shouldUseLaunchSchemeArgsEnv = "YES">
-
-
+ shouldUseLaunchSchemeArgsEnv = "YES"
+ shouldAutocreateTestPlan = "YES">
+ BlueprintIdentifier = "WalletConnectPush"
+ BuildableName = "WalletConnectPush"
+ BlueprintName = "WalletConnectPush"
+ ReferencedContainer = "container:">
diff --git a/Configuration.xcconfig b/Configuration.xcconfig
index fd6cd8553..361714e46 100644
--- a/Configuration.xcconfig
+++ b/Configuration.xcconfig
@@ -9,4 +9,11 @@ RELAY_HOST = relay.walletconnect.com
// Uncomment next line and paste dapp's project secret to run all the notify tests
// GM_DAPP_PROJECT_SECRET = GM_DAPP_PROJECT_SECRET
-CAST_HOST = cast.walletconnect.com
+// Uncomment next line and paste js client's api host to run x-platform tests
+// JS_CLIENT_API_HOST = JS_CLIENT_API_HOST
+
+// WALLETAPP_SENTRY_DSN = WALLETAPP_SENTRY_DSN
+
+// MIXPANEL_TOKEN = MIXPANEL_TOKEN
+
+CAST_HOST = notify.walletconnect.com
diff --git a/Example/DApp/Info.plist b/Example/DApp/Info.plist
index ec622dae1..92cddbe74 100644
--- a/Example/DApp/Info.plist
+++ b/Example/DApp/Info.plist
@@ -2,6 +2,15 @@
+ LSApplicationQueriesSchemes
+
+ metamask
+ trust
+ safe
+ zerion
+ rainbow
+ spot
+
CFBundleVersion
7
CFBundleShortVersionString
diff --git a/Example/DApp/SceneDelegate.swift b/Example/DApp/SceneDelegate.swift
index cb82e0aa3..759bb2df1 100644
--- a/Example/DApp/SceneDelegate.swift
+++ b/Example/DApp/SceneDelegate.swift
@@ -24,9 +24,10 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate {
WalletConnectModal.configure(
projectId: InputConfig.projectId,
- metadata: metadata
+ metadata: metadata,
+ accentColor: .green
)
-
+
setupWindow(scene: scene)
}
diff --git a/Example/DApp/Sign/Accounts/AccountsViewController.swift b/Example/DApp/Sign/Accounts/AccountsViewController.swift
index ad29010ae..631a0725a 100644
--- a/Example/DApp/Sign/Accounts/AccountsViewController.swift
+++ b/Example/DApp/Sign/Accounts/AccountsViewController.swift
@@ -1,6 +1,6 @@
import UIKit
import WalletConnectSign
-import WalletConnectPush
+import WalletConnectNotify
import Combine
struct AccountDetails {
@@ -14,7 +14,7 @@ final class AccountsViewController: UIViewController, UITableViewDataSource, UIT
let session: Session
var accountsDetails: [AccountDetails] = []
var onDisconnect: (() -> Void)?
- var pushSubscription: PushSubscription?
+ var notifySubscription: NotifySubscription?
private var publishers = [AnyCancellable]()
private let accountsView: AccountsView = {
@@ -54,21 +54,7 @@ final class AccountsViewController: UIViewController, UITableViewDataSource, UIT
}
}
}
-
- func proposePushSubscription() {
- let account = session.namespaces.values.first!.accounts.first!
-
- Task(priority: .high){ try! await Push.dapp.propose(account: account, topic: session.pairingTopic)}
- Push.dapp.proposalResponsePublisher.sink { result in
- switch result {
- case .success(let subscription):
- self.pushSubscription = subscription
- case .failure(let error):
- print(error)
- }
- }.store(in: &publishers)
- }
-
+
@objc
private func disconnect() {
Task {
diff --git a/Example/DApp/Sign/ResponseViewController.swift b/Example/DApp/Sign/ResponseViewController.swift
index eec81065f..9d817014b 100644
--- a/Example/DApp/Sign/ResponseViewController.swift
+++ b/Example/DApp/Sign/ResponseViewController.swift
@@ -26,10 +26,10 @@ class ResponseViewController: UIViewController {
let record = Sign.instance.getSessionRequestRecord(id: response.id)!
switch response.result {
case .response(let response):
- responseView.nameLabel.text = "Received Response\n\(record.method)"
+ responseView.nameLabel.text = "Received Response\n\(record.request.method)"
responseView.descriptionLabel.text = try! response.get(String.self).description
case .error(let error):
- responseView.nameLabel.text = "Received Error\n\(record.method)"
+ responseView.nameLabel.text = "Received Error\n\(record.request.method)"
responseView.descriptionLabel.text = error.message
}
responseView.dismissButton.addTarget(self, action: #selector(dismissSelf), for: .touchUpInside)
diff --git a/Example/DApp/Sign/SignCoordinator.swift b/Example/DApp/Sign/SignCoordinator.swift
index 55344a902..763cd2463 100644
--- a/Example/DApp/Sign/SignCoordinator.swift
+++ b/Example/DApp/Sign/SignCoordinator.swift
@@ -45,13 +45,6 @@ final class SignCoordinator {
presentResponse(for: response)
}.store(in: &publishers)
- Sign.instance.sessionSettlePublisher
- .receive(on: DispatchQueue.main)
- .sink { [unowned self] session in
- let vc = showAccountsScreen(session)
- vc.proposePushSubscription()
- }.store(in: &publishers)
-
if let session = Sign.instance.getSessions().first {
_ = showAccountsScreen(session)
} else {
diff --git a/Example/ExampleApp.xcodeproj/IntegrationTests.xctestplan b/Example/ExampleApp.xcodeproj/IntegrationTests.xctestplan
index 64d647c69..0fb2db965 100644
--- a/Example/ExampleApp.xcodeproj/IntegrationTests.xctestplan
+++ b/Example/ExampleApp.xcodeproj/IntegrationTests.xctestplan
@@ -15,6 +15,10 @@
"key" : "RELAY_HOST",
"value" : "$(RELAY_HOST)"
},
+ {
+ "key" : "JS_CLIENT_API_HOST",
+ "value" : "$(JS_CLIENT_API_HOST)"
+ },
{
"key" : "GM_DAPP_PROJECT_SECRET",
"value" : "$(GM_DAPP_PROJECT_SECRET)"
diff --git a/Example/ExampleApp.xcodeproj/project.pbxproj b/Example/ExampleApp.xcodeproj/project.pbxproj
index a9217f3c9..5bf6b7261 100644
--- a/Example/ExampleApp.xcodeproj/project.pbxproj
+++ b/Example/ExampleApp.xcodeproj/project.pbxproj
@@ -32,8 +32,14 @@
847BD1E7298A806800076C90 /* NotificationsInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 847BD1E2298A806800076C90 /* NotificationsInteractor.swift */; };
847BD1E8298A806800076C90 /* NotificationsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 847BD1E3298A806800076C90 /* NotificationsView.swift */; };
847BD1EB298A87AB00076C90 /* SubscriptionsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 847BD1EA298A87AB00076C90 /* SubscriptionsViewModel.swift */; };
- 847CF3AF28E3141700F1D760 /* WalletConnectPush in Frameworks */ = {isa = PBXBuildFile; productRef = 847CF3AE28E3141700F1D760 /* WalletConnectPush */; settings = {ATTRIBUTES = (Required, ); }; };
- 849D7A93292E2169006A2BD4 /* PushTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849D7A92292E2169006A2BD4 /* PushTests.swift */; };
+ 847F08012A25DBFF00B2A5A4 /* XPlatformW3WTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 847F08002A25DBFF00B2A5A4 /* XPlatformW3WTests.swift */; };
+ 8487A9442A836C2A0003D5AF /* Sentry in Frameworks */ = {isa = PBXBuildFile; productRef = 8487A9432A836C2A0003D5AF /* Sentry */; };
+ 8487A9462A836C3F0003D5AF /* Sentry in Frameworks */ = {isa = PBXBuildFile; productRef = 8487A9452A836C3F0003D5AF /* Sentry */; };
+ 8487A9482A83AD680003D5AF /* LoggingService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8487A9472A83AD680003D5AF /* LoggingService.swift */; };
+ 84943C7B2A9BA206007EBAC2 /* Mixpanel in Frameworks */ = {isa = PBXBuildFile; productRef = 84943C7A2A9BA206007EBAC2 /* Mixpanel */; };
+ 84943C7D2A9BA328007EBAC2 /* Mixpanel in Frameworks */ = {isa = PBXBuildFile; productRef = 84943C7C2A9BA328007EBAC2 /* Mixpanel */; };
+ 84943C7F2A9BA48C007EBAC2 /* ProfilingService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84943C7E2A9BA48C007EBAC2 /* ProfilingService.swift */; };
+ 849D7A93292E2169006A2BD4 /* NotifyTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849D7A92292E2169006A2BD4 /* NotifyTests.swift */; };
84A6E3C32A386BBC008A0571 /* Publisher.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84A6E3C22A386BBC008A0571 /* Publisher.swift */; };
84AA01DB28CF0CD7005D48D8 /* XCTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84AA01DA28CF0CD7005D48D8 /* XCTest.swift */; };
84B8154E2991099000FAD54E /* BuildConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84B8154D2991099000FAD54E /* BuildConfiguration.swift */; };
@@ -62,14 +68,6 @@
84DDB4ED28ABB663003D66ED /* WalletConnectAuth in Frameworks */ = {isa = PBXBuildFile; productRef = 84DDB4EC28ABB663003D66ED /* WalletConnectAuth */; };
84E6B84A29787A8000428BAF /* NotificationService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84E6B84929787A8000428BAF /* NotificationService.swift */; };
84E6B84E29787A8000428BAF /* PNDecryptionService.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = 84E6B84729787A8000428BAF /* PNDecryptionService.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
- 84E6B85429787AAE00428BAF /* WalletConnectPush in Frameworks */ = {isa = PBXBuildFile; productRef = 84E6B85329787AAE00428BAF /* WalletConnectPush */; };
- 84E6B8582981624F00428BAF /* PushRequestModule.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84E6B8572981624F00428BAF /* PushRequestModule.swift */; };
- 84E6B85B298162EF00428BAF /* PushRequestPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84E6B85A298162EF00428BAF /* PushRequestPresenter.swift */; };
- 84E6B85D298162F700428BAF /* PushRequestRouter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84E6B85C298162F700428BAF /* PushRequestRouter.swift */; };
- 84E6B85F2981630000428BAF /* PushRequestInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84E6B85E2981630000428BAF /* PushRequestInteractor.swift */; };
- 84E6B8612981630C00428BAF /* PushRequestView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84E6B8602981630C00428BAF /* PushRequestView.swift */; };
- 84E6B86329816A7900428BAF /* WalletConnectPush in Frameworks */ = {isa = PBXBuildFile; productRef = 84E6B86229816A7900428BAF /* WalletConnectPush */; };
- 84E6B8652981720400428BAF /* WalletConnectPush in Frameworks */ = {isa = PBXBuildFile; productRef = 84E6B8642981720400428BAF /* WalletConnectPush */; };
84FE684628ACDB4700C893FF /* RequestParams.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84FE684528ACDB4700C893FF /* RequestParams.swift */; };
A507BE1A29E8032E0038EF70 /* EIP55Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = A507BE1929E8032E0038EF70 /* EIP55Tests.swift */; };
A50C036528AAD32200FE72D3 /* ClientDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = A50C036428AAD32200FE72D3 /* ClientDelegate.swift */; };
@@ -172,6 +170,10 @@
A5A4FC772840C12C00BBEC1E /* RegressionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5A4FC762840C12C00BBEC1E /* RegressionTests.swift */; };
A5A8E47E293A1CFE00FEB97D /* DefaultSignerFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = A59CF4F5292F83D50031A42F /* DefaultSignerFactory.swift */; };
A5A8E480293A1D0000FEB97D /* DefaultSignerFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = A59CF4F5292F83D50031A42F /* DefaultSignerFactory.swift */; };
+ A5B6C0F12A6EAB0800927332 /* WalletConnectNotify in Frameworks */ = {isa = PBXBuildFile; productRef = A5B6C0F02A6EAB0800927332 /* WalletConnectNotify */; };
+ A5B6C0F32A6EAB1700927332 /* WalletConnectNotify in Frameworks */ = {isa = PBXBuildFile; productRef = A5B6C0F22A6EAB1700927332 /* WalletConnectNotify */; };
+ A5B6C0F52A6EAB2800927332 /* WalletConnectNotify in Frameworks */ = {isa = PBXBuildFile; productRef = A5B6C0F42A6EAB2800927332 /* WalletConnectNotify */; };
+ A5B6C0F72A6EAB3200927332 /* WalletConnectNotify in Frameworks */ = {isa = PBXBuildFile; productRef = A5B6C0F62A6EAB3200927332 /* WalletConnectNotify */; };
A5BB7F9F28B69B7100707FC6 /* SignCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5BB7F9E28B69B7100707FC6 /* SignCoordinator.swift */; };
A5BB7FA128B69F3400707FC6 /* AuthCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5BB7FA028B69F3400707FC6 /* AuthCoordinator.swift */; };
A5BB7FA328B6A50400707FC6 /* WalletConnectAuth in Frameworks */ = {isa = PBXBuildFile; productRef = A5BB7FA228B6A50400707FC6 /* WalletConnectAuth */; };
@@ -379,11 +381,14 @@
847BD1E2298A806800076C90 /* NotificationsInteractor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationsInteractor.swift; sourceTree = ""; };
847BD1E3298A806800076C90 /* NotificationsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationsView.swift; sourceTree = ""; };
847BD1EA298A87AB00076C90 /* SubscriptionsViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SubscriptionsViewModel.swift; sourceTree = ""; };
+ 847F08002A25DBFF00B2A5A4 /* XPlatformW3WTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = XPlatformW3WTests.swift; sourceTree = ""; };
+ 8487A92E2A7BD2F30003D5AF /* XPlatformProtocolTests.xctestplan */ = {isa = PBXFileReference; lastKnownFileType = text; name = XPlatformProtocolTests.xctestplan; path = ../XPlatformProtocolTests.xctestplan; sourceTree = ""; };
+ 8487A9472A83AD680003D5AF /* LoggingService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoggingService.swift; sourceTree = ""; };
+ 84943C7E2A9BA48C007EBAC2 /* ProfilingService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfilingService.swift; sourceTree = ""; };
849A4F18298281E300E61ACE /* WalletAppRelease.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = WalletAppRelease.entitlements; sourceTree = ""; };
849A4F19298281F100E61ACE /* PNDecryptionServiceRelease.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = PNDecryptionServiceRelease.entitlements; sourceTree = ""; };
- 849D7A92292E2169006A2BD4 /* PushTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PushTests.swift; sourceTree = ""; };
+ 849D7A92292E2169006A2BD4 /* NotifyTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotifyTests.swift; sourceTree = ""; };
84A6E3C22A386BBC008A0571 /* Publisher.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Publisher.swift; sourceTree = ""; };
- 84A6E3C42A38A5A3008A0571 /* NotifyTests.xctestplan */ = {isa = PBXFileReference; lastKnownFileType = text; name = NotifyTests.xctestplan; path = ../NotifyTests.xctestplan; sourceTree = ""; };
84AA01DA28CF0CD7005D48D8 /* XCTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = XCTest.swift; sourceTree = ""; };
84B8154D2991099000FAD54E /* BuildConfiguration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BuildConfiguration.swift; sourceTree = ""; };
84B8154F2991217900FAD54E /* PushMessagesModule.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PushMessagesModule.swift; sourceTree = ""; };
@@ -414,11 +419,6 @@
84E6B84729787A8000428BAF /* PNDecryptionService.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = PNDecryptionService.appex; sourceTree = BUILT_PRODUCTS_DIR; };
84E6B84929787A8000428BAF /* NotificationService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationService.swift; sourceTree = ""; };
84E6B84B29787A8000428BAF /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
- 84E6B8572981624F00428BAF /* PushRequestModule.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PushRequestModule.swift; sourceTree = ""; };
- 84E6B85A298162EF00428BAF /* PushRequestPresenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PushRequestPresenter.swift; sourceTree = ""; };
- 84E6B85C298162F700428BAF /* PushRequestRouter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PushRequestRouter.swift; sourceTree = ""; };
- 84E6B85E2981630000428BAF /* PushRequestInteractor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PushRequestInteractor.swift; sourceTree = ""; };
- 84E6B8602981630C00428BAF /* PushRequestView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PushRequestView.swift; sourceTree = ""; };
84F568C1279582D200D0A289 /* Signer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Signer.swift; sourceTree = ""; };
84F568C32795832A00D0A289 /* EthereumTransaction.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EthereumTransaction.swift; sourceTree = ""; };
84FE684528ACDB4700C893FF /* RequestParams.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RequestParams.swift; sourceTree = ""; };
@@ -617,6 +617,7 @@
CF6704DE29E59DDC003326A4 /* XCUIElementQuery.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = XCUIElementQuery.swift; sourceTree = ""; };
CF6704E029E5A014003326A4 /* XCTestCase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = XCTestCase.swift; sourceTree = ""; };
CF79389D29EDD9DC00441B4F /* RelayIntegrationTests.xctestplan */ = {isa = PBXFileReference; lastKnownFileType = text; path = RelayIntegrationTests.xctestplan; sourceTree = ""; };
+ CFF161B82A69719F00004342 /* WalletConnect-Package.xctestplan */ = {isa = PBXFileReference; lastKnownFileType = text; path = "WalletConnect-Package.xctestplan"; sourceTree = ""; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
@@ -635,8 +636,11 @@
C5CFBECC2AA99D5D00378D41 /* Starscream in Frameworks */,
8448F1D427E4726F0000B866 /* WalletConnect in Frameworks */,
CF25F28B2A432488009C7E49 /* WalletConnectModal in Frameworks */,
+ A5B6C0F12A6EAB0800927332 /* WalletConnectNotify in Frameworks */,
A54195A52934E83F0035AD19 /* Web3 in Frameworks */,
- 84E6B8652981720400428BAF /* WalletConnectPush in Frameworks */,
+ 8487A9442A836C2A0003D5AF /* Sentry in Frameworks */,
+ A5D85228286333E300DAF5C3 /* Starscream in Frameworks */,
+ 84943C7B2A9BA206007EBAC2 /* Mixpanel in Frameworks */,
A573C53929EC365000E3CBFD /* HDWalletKit in Frameworks */,
A5BB7FA328B6A50400707FC6 /* WalletConnectAuth in Frameworks */,
);
@@ -646,7 +650,7 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
- 84E6B86329816A7900428BAF /* WalletConnectPush in Frameworks */,
+ A5B6C0F72A6EAB3200927332 /* WalletConnectNotify in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -678,10 +682,10 @@
A5E03DFF2864662500888481 /* WalletConnect in Frameworks */,
A561C80529DFCD4500DF540D /* WalletConnectSync in Frameworks */,
A50DF19D2A25084A0036EA6C /* WalletConnectHistory in Frameworks */,
- 847CF3AF28E3141700F1D760 /* WalletConnectPush in Frameworks */,
A5C8BE85292FE20B006CC85C /* Web3 in Frameworks */,
84DDB4ED28ABB663003D66ED /* WalletConnectAuth in Frameworks */,
C5DD5BE1294E09E3008FD3A4 /* Web3Wallet in Frameworks */,
+ A5B6C0F32A6EAB1700927332 /* WalletConnectNotify in Frameworks */,
A573C53B29EC365800E3CBFD /* HDWalletKit in Frameworks */,
A5E03E01286466EA00888481 /* WalletConnectChat in Frameworks */,
);
@@ -696,9 +700,12 @@
C5133A78294125CC00A8314C /* Web3 in Frameworks */,
84536D7429EEBCF0008EA8DB /* Web3Inbox in Frameworks */,
C5B2F7052970573D000DBA0E /* SolanaSwift in Frameworks */,
+ 8487A9462A836C3F0003D5AF /* Sentry in Frameworks */,
C55D349929630D440004314A /* Web3Wallet in Frameworks */,
- 84E6B85429787AAE00428BAF /* WalletConnectPush in Frameworks */,
+ C56EE255293F569A004840D1 /* Starscream in Frameworks */,
+ A5B6C0F52A6EAB2800927332 /* WalletConnectNotify in Frameworks */,
C56EE27B293F56F8004840D1 /* WalletConnectAuth in Frameworks */,
+ 84943C7D2A9BA328007EBAC2 /* Mixpanel in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -715,10 +722,11 @@
764E1D3326F8D3FC00A1FB15 = {
isa = PBXGroup;
children = (
+ CFF161B82A69719F00004342 /* WalletConnect-Package.xctestplan */,
CF79389D29EDD9DC00441B4F /* RelayIntegrationTests.xctestplan */,
845AA7D929BA1EBA00F33739 /* IntegrationTests.xctestplan */,
- 84A6E3C42A38A5A3008A0571 /* NotifyTests.xctestplan */,
845AA7DC29BB424800F33739 /* SmokeTests.xctestplan */,
+ 8487A92E2A7BD2F30003D5AF /* XPlatformProtocolTests.xctestplan */,
A5A8E479293A1C4400FEB97D /* Shared */,
A5F48A0528E43D3F0034CBFB /* Configuration.xcconfig */,
84CE6453279FFE1100142511 /* Wallet.entitlements */,
@@ -830,10 +838,26 @@
path = Models;
sourceTree = "";
};
+ 847F07FE2A25DBC700B2A5A4 /* XPlatform */ = {
+ isa = PBXGroup;
+ children = (
+ 847F07FF2A25DBDB00B2A5A4 /* Web3Wallet */,
+ );
+ path = XPlatform;
+ sourceTree = "";
+ };
+ 847F07FF2A25DBDB00B2A5A4 /* Web3Wallet */ = {
+ isa = PBXGroup;
+ children = (
+ 847F08002A25DBFF00B2A5A4 /* XPlatformW3WTests.swift */,
+ );
+ path = Web3Wallet;
+ sourceTree = "";
+ };
849D7A91292E2115006A2BD4 /* Push */ = {
isa = PBXGroup;
children = (
- 849D7A92292E2169006A2BD4 /* PushTests.swift */,
+ 849D7A92292E2169006A2BD4 /* NotifyTests.swift */,
84A6E3C22A386BBC008A0571 /* Publisher.swift */,
);
path = Push;
@@ -949,18 +973,6 @@
path = PNDecryptionService;
sourceTree = "";
};
- 84E6B8592981625A00428BAF /* PushRequest */ = {
- isa = PBXGroup;
- children = (
- 84E6B8572981624F00428BAF /* PushRequestModule.swift */,
- 84E6B85A298162EF00428BAF /* PushRequestPresenter.swift */,
- 84E6B85C298162F700428BAF /* PushRequestRouter.swift */,
- 84E6B85E2981630000428BAF /* PushRequestInteractor.swift */,
- 84E6B8602981630C00428BAF /* PushRequestView.swift */,
- );
- path = PushRequest;
- sourceTree = "";
- };
A50F3944288005A700064555 /* Types */ = {
isa = PBXGroup;
children = (
@@ -1410,6 +1422,7 @@
A5E03DEE286464DB00888481 /* IntegrationTests */ = {
isa = PBXGroup;
children = (
+ 847F07FE2A25DBC700B2A5A4 /* XPlatform */,
A5321C292A25035A006CADC3 /* History */,
A561C80129DFCCD300DF540D /* Sync */,
849D7A91292E2115006A2BD4 /* Push */,
@@ -1543,7 +1556,6 @@
C5F32A2A2954812900A6476E /* ConnectionDetails */,
C56EE236293F566A004840D1 /* Scan */,
C56EE22A293F5668004840D1 /* Wallet */,
- 84E6B8592981625A00428BAF /* PushRequest */,
847BD1E9298A807000076C90 /* Notifications */,
84B815592991217F00FAD54E /* PushMessages */,
);
@@ -1652,6 +1664,8 @@
C56EE281293F5757004840D1 /* SceneDelegate.swift */,
84DB38F22983CDAE00BFEE37 /* PushRegisterer.swift */,
A51811972A52E21A00A52B15 /* ConfigurationService.swift */,
+ 8487A9472A83AD680003D5AF /* LoggingService.swift */,
+ 84943C7E2A9BA48C007EBAC2 /* ProfilingService.swift */,
);
path = ApplicationLayer;
sourceTree = "";
@@ -1800,10 +1814,12 @@
8448F1D327E4726F0000B866 /* WalletConnect */,
A5BB7FA228B6A50400707FC6 /* WalletConnectAuth */,
A54195A42934E83F0035AD19 /* Web3 */,
- 84E6B8642981720400428BAF /* WalletConnectPush */,
A573C53829EC365000E3CBFD /* HDWalletKit */,
CF25F28A2A432488009C7E49 /* WalletConnectModal */,
C5CFBECB2AA99D5D00378D41 /* Starscream */,
+ 8487A9432A836C2A0003D5AF /* Sentry */,
+ A5B6C0F02A6EAB0800927332 /* WalletConnectNotify */,
+ 84943C7A2A9BA206007EBAC2 /* Mixpanel */,
);
productName = DApp;
productReference = 84CE641C27981DED00142511 /* DApp.app */;
@@ -1823,7 +1839,7 @@
);
name = PNDecryptionService;
packageProductDependencies = (
- 84E6B86229816A7900428BAF /* WalletConnectPush */,
+ A5B6C0F62A6EAB3200927332 /* WalletConnectNotify */,
);
productName = PNDecryptionService;
productReference = 84E6B84729787A8000428BAF /* PNDecryptionService.appex */;
@@ -1890,12 +1906,12 @@
A5E03DFE2864662500888481 /* WalletConnect */,
A5E03E00286466EA00888481 /* WalletConnectChat */,
84DDB4EC28ABB663003D66ED /* WalletConnectAuth */,
- 847CF3AE28E3141700F1D760 /* WalletConnectPush */,
A5C8BE84292FE20B006CC85C /* Web3 */,
C5DD5BE0294E09E3008FD3A4 /* Web3Wallet */,
A561C80429DFCD4500DF540D /* WalletConnectSync */,
A573C53A29EC365800E3CBFD /* HDWalletKit */,
A50DF19C2A25084A0036EA6C /* WalletConnectHistory */,
+ A5B6C0F22A6EAB1700927332 /* WalletConnectNotify */,
);
productName = IntegrationTests;
productReference = A5E03DED286464DB00888481 /* IntegrationTests.xctest */;
@@ -1922,9 +1938,11 @@
C5133A77294125CC00A8314C /* Web3 */,
C55D349829630D440004314A /* Web3Wallet */,
C5B2F7042970573D000DBA0E /* SolanaSwift */,
- 84E6B85329787AAE00428BAF /* WalletConnectPush */,
84536D7329EEBCF0008EA8DB /* Web3Inbox */,
A573C53C29EC366500E3CBFD /* HDWalletKit */,
+ 8487A9452A836C3F0003D5AF /* Sentry */,
+ A5B6C0F42A6EAB2800927332 /* WalletConnectNotify */,
+ 84943C7C2A9BA328007EBAC2 /* Mixpanel */,
);
productName = ChatWallet;
productReference = C56EE21B293F55ED004840D1 /* WalletApp.app */;
@@ -2000,6 +2018,8 @@
A58EC60F299D57B800F3452A /* XCRemoteSwiftPackageReference "swiftui-async-button" */,
A561C7FE29DF32CE00DF540D /* XCRemoteSwiftPackageReference "HDWallet" */,
C5CFBECA2AA99D5D00378D41 /* XCRemoteSwiftPackageReference "Starscream" */,
+ 8487A9422A836C2A0003D5AF /* XCRemoteSwiftPackageReference "sentry-cocoa" */,
+ 84943C792A9BA206007EBAC2 /* XCRemoteSwiftPackageReference "mixpanel-swift" */,
);
productRefGroup = 764E1D3D26F8D3FC00A1FB15 /* Products */;
projectDirPath = "";
@@ -2237,8 +2257,9 @@
A518B31428E33A6500A2CE93 /* InputConfig.swift in Sources */,
A541959E2934BFEF0035AD19 /* CacaoSignerTests.swift in Sources */,
A59CF4F6292F83D50031A42F /* DefaultSignerFactory.swift in Sources */,
+ 847F08012A25DBFF00B2A5A4 /* XPlatformW3WTests.swift in Sources */,
A5E03E03286466F400888481 /* ChatTests.swift in Sources */,
- 849D7A93292E2169006A2BD4 /* PushTests.swift in Sources */,
+ 849D7A93292E2169006A2BD4 /* NotifyTests.swift in Sources */,
845B8D8C2934B36C0084A966 /* Account.swift in Sources */,
84D2A66628A4F51E0088AE09 /* AuthTests.swift in Sources */,
84FE684628ACDB4700C893FF /* RequestParams.swift in Sources */,
@@ -2263,9 +2284,7 @@
buildActionMask = 2147483647;
files = (
C53AA4362941251C008EA57C /* DefaultSignerFactory.swift in Sources */,
- 84E6B8582981624F00428BAF /* PushRequestModule.swift in Sources */,
C55D3480295DD7140004314A /* AuthRequestPresenter.swift in Sources */,
- 84E6B85B298162EF00428BAF /* PushRequestPresenter.swift in Sources */,
A51811A02A52E83100A52B15 /* SettingsPresenter.swift in Sources */,
847BD1DA2989492500076C90 /* MainRouter.swift in Sources */,
C5F32A2E2954814A00A6476E /* ConnectionDetailsRouter.swift in Sources */,
@@ -2316,8 +2335,6 @@
A51811A12A52E83100A52B15 /* SettingsRouter.swift in Sources */,
C56EE279293F56D7004840D1 /* Color.swift in Sources */,
847BD1E6298A806800076C90 /* NotificationsRouter.swift in Sources */,
- 84E6B8612981630C00428BAF /* PushRequestView.swift in Sources */,
- 84E6B85F2981630000428BAF /* PushRequestInteractor.swift in Sources */,
C55D3483295DD7140004314A /* AuthRequestView.swift in Sources */,
C56EE243293F566D004840D1 /* ScanView.swift in Sources */,
84310D05298BC980000C15B6 /* MainInteractor.swift in Sources */,
@@ -2350,13 +2367,14 @@
C5B2F6F929705293000DBA0E /* SessionRequestPresenter.swift in Sources */,
A57879712A4EDC8100F8D10B /* TextFieldView.swift in Sources */,
84DB38F32983CDAE00BFEE37 /* PushRegisterer.swift in Sources */,
+ 84943C7F2A9BA48C007EBAC2 /* ProfilingService.swift in Sources */,
C5B2F6FB297055B0000DBA0E /* ETHSigner.swift in Sources */,
C56EE274293F56D7004840D1 /* SceneViewController.swift in Sources */,
847BD1E5298A806800076C90 /* NotificationsPresenter.swift in Sources */,
C55D3496295DFA750004314A /* WelcomeInteractor.swift in Sources */,
- 84E6B85D298162F700428BAF /* PushRequestRouter.swift in Sources */,
C5B2F6FC297055B0000DBA0E /* SOLSigner.swift in Sources */,
A518119F2A52E83100A52B15 /* SettingsModule.swift in Sources */,
+ 8487A9482A83AD680003D5AF /* LoggingService.swift in Sources */,
C55D348D295DD8CA0004314A /* PasteUriView.swift in Sources */,
C5F32A2C2954814200A6476E /* ConnectionDetailsModule.swift in Sources */,
C56EE249293F566D004840D1 /* ScanInteractor.swift in Sources */,
@@ -3073,6 +3091,22 @@
/* End XCConfigurationList section */
/* Begin XCRemoteSwiftPackageReference section */
+ 8487A9422A836C2A0003D5AF /* XCRemoteSwiftPackageReference "sentry-cocoa" */ = {
+ isa = XCRemoteSwiftPackageReference;
+ repositoryURL = "https://github.com/getsentry/sentry-cocoa.git";
+ requirement = {
+ kind = upToNextMajorVersion;
+ minimumVersion = 8.0.0;
+ };
+ };
+ 84943C792A9BA206007EBAC2 /* XCRemoteSwiftPackageReference "mixpanel-swift" */ = {
+ isa = XCRemoteSwiftPackageReference;
+ repositoryURL = "https://github.com/mixpanel/mixpanel-swift";
+ requirement = {
+ branch = master;
+ kind = branch;
+ };
+ };
A5434021291E6A270068F706 /* XCRemoteSwiftPackageReference "solana-swift" */ = {
isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/flypaper0/solana-swift";
@@ -3128,25 +3162,29 @@
isa = XCSwiftPackageProductDependency;
productName = Web3Inbox;
};
- 847CF3AE28E3141700F1D760 /* WalletConnectPush */ = {
+ 8487A9432A836C2A0003D5AF /* Sentry */ = {
isa = XCSwiftPackageProductDependency;
- productName = WalletConnectPush;
+ package = 8487A9422A836C2A0003D5AF /* XCRemoteSwiftPackageReference "sentry-cocoa" */;
+ productName = Sentry;
};
- 84DDB4EC28ABB663003D66ED /* WalletConnectAuth */ = {
+ 8487A9452A836C3F0003D5AF /* Sentry */ = {
isa = XCSwiftPackageProductDependency;
- productName = WalletConnectAuth;
+ package = 8487A9422A836C2A0003D5AF /* XCRemoteSwiftPackageReference "sentry-cocoa" */;
+ productName = Sentry;
};
- 84E6B85329787AAE00428BAF /* WalletConnectPush */ = {
+ 84943C7A2A9BA206007EBAC2 /* Mixpanel */ = {
isa = XCSwiftPackageProductDependency;
- productName = WalletConnectPush;
+ package = 84943C792A9BA206007EBAC2 /* XCRemoteSwiftPackageReference "mixpanel-swift" */;
+ productName = Mixpanel;
};
- 84E6B86229816A7900428BAF /* WalletConnectPush */ = {
+ 84943C7C2A9BA328007EBAC2 /* Mixpanel */ = {
isa = XCSwiftPackageProductDependency;
- productName = WalletConnectPush;
+ package = 84943C792A9BA206007EBAC2 /* XCRemoteSwiftPackageReference "mixpanel-swift" */;
+ productName = Mixpanel;
};
- 84E6B8642981720400428BAF /* WalletConnectPush */ = {
+ 84DDB4EC28ABB663003D66ED /* WalletConnectAuth */ = {
isa = XCSwiftPackageProductDependency;
- productName = WalletConnectPush;
+ productName = WalletConnectAuth;
};
A50DF19C2A25084A0036EA6C /* WalletConnectHistory */ = {
isa = XCSwiftPackageProductDependency;
@@ -3203,6 +3241,22 @@
package = A5AE354528A1A2AC0059AE8A /* XCRemoteSwiftPackageReference "Web3" */;
productName = Web3;
};
+ A5B6C0F02A6EAB0800927332 /* WalletConnectNotify */ = {
+ isa = XCSwiftPackageProductDependency;
+ productName = WalletConnectNotify;
+ };
+ A5B6C0F22A6EAB1700927332 /* WalletConnectNotify */ = {
+ isa = XCSwiftPackageProductDependency;
+ productName = WalletConnectNotify;
+ };
+ A5B6C0F42A6EAB2800927332 /* WalletConnectNotify */ = {
+ isa = XCSwiftPackageProductDependency;
+ productName = WalletConnectNotify;
+ };
+ A5B6C0F62A6EAB3200927332 /* WalletConnectNotify */ = {
+ isa = XCSwiftPackageProductDependency;
+ productName = WalletConnectNotify;
+ };
A5BB7FA228B6A50400707FC6 /* WalletConnectAuth */ = {
isa = XCSwiftPackageProductDependency;
productName = WalletConnectAuth;
diff --git a/Example/ExampleApp.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/Example/ExampleApp.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved
index 53171f58a..d4ad48aad 100644
--- a/Example/ExampleApp.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved
+++ b/Example/ExampleApp.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved
@@ -28,6 +28,15 @@
"version": null
}
},
+ {
+ "package": "Mixpanel",
+ "repositoryURL": "https://github.com/mixpanel/mixpanel-swift",
+ "state": {
+ "branch": "master",
+ "revision": "1ce27d937009d5ecce74dad97d69898ffea49c75",
+ "version": null
+ }
+ },
{
"package": "PromiseKit",
"repositoryURL": "https://github.com/mxcl/PromiseKit.git",
@@ -55,6 +64,15 @@
"version": "0.1.7"
}
},
+ {
+ "package": "Sentry",
+ "repositoryURL": "https://github.com/getsentry/sentry-cocoa.git",
+ "state": {
+ "branch": null,
+ "revision": "12998398eb51e2e8ff7098163fa97d305eee6d87",
+ "version": "8.11.0"
+ }
+ },
{
"package": "SolanaSwift",
"repositoryURL": "https://github.com/flypaper0/solana-swift",
diff --git a/Example/ExampleApp.xcodeproj/xcshareddata/xcschemes/BuildAll.xcscheme b/Example/ExampleApp.xcodeproj/xcshareddata/xcschemes/BuildAll.xcscheme
index 9bdb81b02..6b4a6df22 100644
--- a/Example/ExampleApp.xcodeproj/xcshareddata/xcschemes/BuildAll.xcscheme
+++ b/Example/ExampleApp.xcodeproj/xcshareddata/xcschemes/BuildAll.xcscheme
@@ -83,6 +83,9 @@
+
+
+
+
+
+
+
+
diff --git a/Example/ExampleApp.xcodeproj/xcshareddata/xcschemes/WalletConnect.xcscheme b/Example/ExampleApp.xcodeproj/xcshareddata/xcschemes/WalletConnect.xcscheme
index aa58da974..4f51ebe1b 100644
--- a/Example/ExampleApp.xcodeproj/xcshareddata/xcschemes/WalletConnect.xcscheme
+++ b/Example/ExampleApp.xcodeproj/xcshareddata/xcschemes/WalletConnect.xcscheme
@@ -288,6 +288,16 @@
ReferencedContainer = "container:..">
+
+
+
+
(PairingClient, AuthClient) {
- let logger = ConsoleLogger(suffix: prefix, loggingLevel: .debug)
+ let logger = ConsoleLogger(prefix: prefix, loggingLevel: .debug)
let keyValueStorage = RuntimeKeyValueStorage()
let keychain = KeychainStorageMock()
let relayClient = RelayClientFactory.create(relayHost: InputConfig.relayHost, projectId: InputConfig.projectId, keyValueStorage: RuntimeKeyValueStorage(), keychainStorage: keychain, logger: logger)
@@ -70,7 +70,7 @@ final class AuthTests: XCTestCase {
let uri = try! await appPairingClient.create()
try! await appAuthClient.request(RequestParams.stub(), topic: uri.topic)
- try! await walletPairingClient.pair(uri: uri)
+ try? await walletPairingClient.pair(uri: uri)
walletAuthClient.authRequestPublisher.sink { _ in
requestExpectation.fulfill()
}.store(in: &publishers)
@@ -82,7 +82,7 @@ final class AuthTests: XCTestCase {
let uri = try! await appPairingClient.create()
try! await appAuthClient.request(RequestParams.stub(), topic: uri.topic)
- try! await walletPairingClient.pair(uri: uri)
+ try? await walletPairingClient.pair(uri: uri)
walletAuthClient.authRequestPublisher.sink { [unowned self] request in
Task(priority: .high) {
let signerFactory = DefaultSignerFactory()
@@ -120,7 +120,7 @@ final class AuthTests: XCTestCase {
resources: nil
), topic: uri.topic)
- try! await walletPairingClient.pair(uri: uri)
+ try? await walletPairingClient.pair(uri: uri)
walletAuthClient.authRequestPublisher.sink { [unowned self] request in
Task(priority: .high) {
let signature = CacaoSignature(t: .eip1271, s: eip1271Signature)
@@ -141,7 +141,7 @@ final class AuthTests: XCTestCase {
let uri = try! await appPairingClient.create()
try! await appAuthClient.request(RequestParams.stub(), topic: uri.topic)
- try! await walletPairingClient.pair(uri: uri)
+ try? await walletPairingClient.pair(uri: uri)
walletAuthClient.authRequestPublisher.sink { [unowned self] request in
Task(priority: .high) {
let signature = CacaoSignature(t: .eip1271, s: eip1271Signature)
@@ -162,7 +162,7 @@ final class AuthTests: XCTestCase {
let uri = try! await appPairingClient.create()
try! await appAuthClient.request(RequestParams.stub(), topic: uri.topic)
- try! await walletPairingClient.pair(uri: uri)
+ try? await walletPairingClient.pair(uri: uri)
walletAuthClient.authRequestPublisher.sink { [unowned self] request in
Task(priority: .high) {
try! await walletAuthClient.reject(requestId: request.0.id)
@@ -183,7 +183,7 @@ final class AuthTests: XCTestCase {
let uri = try! await appPairingClient.create()
try! await appAuthClient.request(RequestParams.stub(), topic: uri.topic)
- try! await walletPairingClient.pair(uri: uri)
+ try? await walletPairingClient.pair(uri: uri)
walletAuthClient.authRequestPublisher.sink { [unowned self] request in
Task(priority: .high) {
let invalidSignature = "438effc459956b57fcd9f3dac6c675f9cee88abf21acab7305e8e32aa0303a883b06dcbd956279a7a2ca21ffa882ff55cc22e8ab8ec0f3fe90ab45f306938cfa1b"
@@ -201,9 +201,3 @@ final class AuthTests: XCTestCase {
wait(for: [responseExpectation], timeout: InputConfig.defaultTimeout)
}
}
-
-private struct IATProviderMock: IATProvider {
- var iat: String {
- return "2022-10-10T23:03:35.700Z"
- }
-}
diff --git a/Example/IntegrationTests/Auth/ENS/ENSResolverTests.swift b/Example/IntegrationTests/Auth/ENS/ENSResolverTests.swift
index 8ea3661e8..6cced3613 100644
--- a/Example/IntegrationTests/Auth/ENS/ENSResolverTests.swift
+++ b/Example/IntegrationTests/Auth/ENS/ENSResolverTests.swift
@@ -7,16 +7,16 @@ class ENSResolverTests: XCTestCase {
private let account = Account("eip155:1:0xD02D090F8f99B61D65d8e8876Ea86c2720aB27BC")!
private let ens = "web3.eth"
-// Note: - removed until RPC server fix
-// func testResolveEns() async throws {
-// let resolver = ENSResolverFactory(crypto: DefaultCryptoProvider()).create(projectId: InputConfig.projectId)
-// let resolved = try await resolver.resolveEns(account: account)
-// XCTAssertEqual(resolved, ens)
-// }
-//
-// func testResolveAddress() async throws {
-// let resolver = ENSResolverFactory(crypto: DefaultCryptoProvider()).create(projectId: InputConfig.projectId)
-// let resolved = try await resolver.resolveAddress(ens: ens, blockchain: account.blockchain)
-// XCTAssertEqual(resolved, account)
-// }
+ // Note: - removed until RPC server fix
+ // func testResolveEns() async throws {
+ // let resolver = ENSResolverFactory(crypto: DefaultCryptoProvider()).create(projectId: InputConfig.projectId)
+ // let resolved = try await resolver.resolveEns(account: account)
+ // XCTAssertEqual(resolved, ens)
+ // }
+ //
+ // func testResolveAddress() async throws {
+ // let resolver = ENSResolverFactory(crypto: DefaultCryptoProvider()).create(projectId: InputConfig.projectId)
+ // let resolved = try await resolver.resolveAddress(ens: ens, blockchain: account.blockchain)
+ // XCTAssertEqual(resolved, account)
+ // }
}
diff --git a/Example/IntegrationTests/Chat/ChatTests.swift b/Example/IntegrationTests/Chat/ChatTests.swift
index 7c69c508e..d4817e619 100644
--- a/Example/IntegrationTests/Chat/ChatTests.swift
+++ b/Example/IntegrationTests/Chat/ChatTests.swift
@@ -48,7 +48,7 @@ final class ChatTests: XCTestCase {
func makeClient(prefix: String, account: Account) -> ChatClient {
let keyserverURL = URL(string: "https://keys.walletconnect.com")!
- let logger = ConsoleLogger(suffix: prefix, loggingLevel: .debug)
+ let logger = ConsoleLogger(prefix: prefix, loggingLevel: .debug)
let keyValueStorage = RuntimeKeyValueStorage()
let keychain = KeychainStorageMock()
let relayClient = RelayClientFactory.create(relayHost: InputConfig.relayHost, projectId: InputConfig.projectId, keyValueStorage: RuntimeKeyValueStorage(), keychainStorage: keychain, logger: logger)
diff --git a/Example/IntegrationTests/Chat/RegistryTests.swift b/Example/IntegrationTests/Chat/RegistryTests.swift
index 074251e63..a6de83f9e 100644
--- a/Example/IntegrationTests/Chat/RegistryTests.swift
+++ b/Example/IntegrationTests/Chat/RegistryTests.swift
@@ -32,27 +32,27 @@ final class RegistryTests: XCTestCase {
signer = MessageSignerFactory(signerFactory: DefaultSignerFactory()).create(projectId: InputConfig.projectId)
}
- func testRegisterIdentityAndInviteKey() async throws {
- let publicKey = try await sut.registerIdentity(account: account, onSign: onSign)
-
- let iss = DIDKey(rawData: Data(hex: publicKey)).did(variant: .ED25519)
- let resolvedAccount = try await sut.resolveIdentity(iss: iss)
- XCTAssertEqual(resolvedAccount, account)
-
- let recovered = try storage.getIdentityKey(for: account).publicKey.hexRepresentation
- XCTAssertEqual(publicKey, recovered)
-
- let inviteKey = try await sut.registerInvite(account: account)
-
- let recoveredKey = try storage.getInviteKey(for: account)
- XCTAssertEqual(inviteKey, recoveredKey)
-
- let resolvedKey = try await sut.resolveInvite(account: account)
- XCTAssertEqual(inviteKey.did, resolvedKey)
-
- _ = try await sut.goPrivate(account: account)
- try await sut.unregister(account: account, onSign: onSign)
- }
+// func testRegisterIdentityAndInviteKey() async throws {
+// let publicKey = try await sut.registerIdentity(account: account, onSign: onSign)
+//
+// let iss = DIDKey(rawData: Data(hex: publicKey)).did(variant: .ED25519)
+// let resolvedAccount = try await sut.resolveIdentity(iss: iss)
+// XCTAssertEqual(resolvedAccount, account)
+//
+// let recovered = try storage.getIdentityKey(for: account).publicKey.hexRepresentation
+// XCTAssertEqual(publicKey, recovered)
+//
+// let inviteKey = try await sut.registerInvite(account: account)
+//
+// let recoveredKey = try storage.getInviteKey(for: account)
+// XCTAssertEqual(inviteKey, recoveredKey)
+//
+// let resolvedKey = try await sut.resolveInvite(account: account)
+// XCTAssertEqual(inviteKey.did, resolvedKey)
+//
+// _ = try await sut.goPrivate(account: account)
+// try await sut.unregister(account: account, onSign: onSign)
+// }
}
private extension RegistryTests {
diff --git a/Example/IntegrationTests/History/HistoryTests.swift b/Example/IntegrationTests/History/HistoryTests.swift
index 00c0a01f6..40d52f5c0 100644
--- a/Example/IntegrationTests/History/HistoryTests.swift
+++ b/Example/IntegrationTests/History/HistoryTests.swift
@@ -29,7 +29,8 @@ final class HistoryTests: XCTestCase {
projectId: InputConfig.projectId,
keyValueStorage: RuntimeKeyValueStorage(),
keychainStorage: keychain,
- logger: ConsoleLogger(suffix: prefix + " [Relay]", loggingLevel: .debug))
+ socketFactory: DefaultSocketFactory(),
+ logger: ConsoleLogger(prefix: prefix + " [Relay]", loggingLevel: .debug))
}
private func makeHistoryClient(keychain: KeychainStorageProtocol) -> HistoryNetworkService {
diff --git a/Example/IntegrationTests/Pairing/PairingTests.swift b/Example/IntegrationTests/Pairing/PairingTests.swift
index 502cb5a44..0c9e8f523 100644
--- a/Example/IntegrationTests/Pairing/PairingTests.swift
+++ b/Example/IntegrationTests/Pairing/PairingTests.swift
@@ -1,12 +1,12 @@
import Foundation
import XCTest
-import WalletConnectUtils
+@testable import WalletConnectUtils
@testable import WalletConnectKMS
import WalletConnectRelay
import Combine
import WalletConnectNetworking
-import WalletConnectEcho
-@testable import WalletConnectPush
+import WalletConnectPush
+@testable import Auth
@testable import WalletConnectPairing
@testable import WalletConnectSync
@testable import WalletConnectHistory
@@ -16,20 +16,20 @@ final class PairingTests: XCTestCase {
var appPairingClient: PairingClient!
var walletPairingClient: PairingClient!
- var appPushClient: DappPushClient!
- var walletPushClient: WalletPushClient!
+ var appAuthClient: AuthClient!
+ var walletAuthClient: AuthClient!
var pairingStorage: PairingStorage!
private var publishers = [AnyCancellable]()
- func makeClientDependencies(prefix: String) -> (PairingClient, NetworkInteracting, SyncClient, KeychainStorageProtocol, KeyValueStorage) {
+ func makeClientDependencies(prefix: String) -> (PairingClient, NetworkingInteractor, KeychainStorageProtocol, KeyValueStorage) {
let keychain = KeychainStorageMock()
let keyValueStorage = RuntimeKeyValueStorage()
- let relayLogger = ConsoleLogger(suffix: prefix + " [Relay]", loggingLevel: .debug)
- let pairingLogger = ConsoleLogger(suffix: prefix + " [Pairing]", loggingLevel: .debug)
- let networkingLogger = ConsoleLogger(suffix: prefix + " [Networking]", loggingLevel: .debug)
+ let relayLogger = ConsoleLogger(prefix: prefix + " [Relay]", loggingLevel: .debug)
+ let pairingLogger = ConsoleLogger(prefix: prefix + " [Pairing]", loggingLevel: .debug)
+ let networkingLogger = ConsoleLogger(prefix: prefix + " [Networking]", loggingLevel: .debug)
let relayClient = RelayClientFactory.create(
relayHost: InputConfig.relayHost,
@@ -49,59 +49,55 @@ final class PairingTests: XCTestCase {
keyValueStorage: keyValueStorage,
keychainStorage: keychain,
networkingClient: networkingClient)
-
- let syncClient = SyncClientFactory.create(networkInteractor: networkingClient, bip44: DefaultBIP44Provider(), keychain: keychain)
-
let clientId = try! networkingClient.getClientId()
networkingLogger.debug("My client id is: \(clientId)")
- return (pairingClient, networkingClient, syncClient, keychain, keyValueStorage)
+ return (pairingClient, networkingClient, keychain, keyValueStorage)
}
func makeDappClients() {
let prefix = "🤖 Dapp: "
- let (pairingClient, networkingInteractor, syncClient, keychain, keyValueStorage) = makeClientDependencies(prefix: prefix)
- let pushLogger = ConsoleLogger(suffix: prefix + " [Push]", loggingLevel: .debug)
+ let (pairingClient, networkingInteractor, keychain, keyValueStorage) = makeClientDependencies(prefix: prefix)
+ let notifyLogger = ConsoleLogger(prefix: prefix + " [Notify]", loggingLevel: .debug)
appPairingClient = pairingClient
- appPushClient = DappPushClientFactory.create(metadata: AppMetadata(name: name, description: "", url: "", icons: [""]),
- logger: pushLogger,
- keyValueStorage: keyValueStorage,
- keychainStorage: keychain,
- groupKeychainStorage: KeychainStorageMock(),
- networkInteractor: networkingInteractor,
- syncClient: syncClient)
+
+ appAuthClient = AuthClientFactory.create(
+ metadata: AppMetadata(name: name, description: "", url: "", icons: [""]),
+ projectId: InputConfig.projectId,
+ crypto: DefaultCryptoProvider(),
+ logger: notifyLogger,
+ keyValueStorage: keyValueStorage,
+ keychainStorage: keychain,
+ networkingClient: networkingInteractor,
+ pairingRegisterer: pairingClient,
+ iatProvider: IATProviderMock())
}
func makeWalletClients() {
let prefix = "🐶 Wallet: "
- let (pairingClient, networkingInteractor, syncClient, keychain, keyValueStorage) = makeClientDependencies(prefix: prefix)
- let pushLogger = ConsoleLogger(suffix: prefix + " [Push]", loggingLevel: .debug)
+ let (pairingClient, networkingInteractor, keychain, keyValueStorage) = makeClientDependencies(prefix: prefix)
+ let notifyLogger = ConsoleLogger(prefix: prefix + " [Notify]", loggingLevel: .debug)
walletPairingClient = pairingClient
- let echoClient = EchoClientFactory.create(projectId: "",
- echoHost: "echo.walletconnect.com",
- keychainStorage: keychain,
- environment: .sandbox)
- let keyserverURL = URL(string: "https://keys.walletconnect.com")!
let historyClient = HistoryClientFactory.create(
historyUrl: "https://history.walletconnect.com",
relayUrl: "wss://relay.walletconnect.com",
keychain: keychain
)
- walletPushClient = WalletPushClientFactory.create(keyserverURL: keyserverURL,
- logger: pushLogger,
- keyValueStorage: keyValueStorage,
- keychainStorage: keychain,
- groupKeychainStorage: KeychainStorageMock(),
- networkInteractor: networkingInteractor,
- pairingRegisterer: pairingClient,
- echoClient: echoClient,
- syncClient: syncClient,
- historyClient: historyClient)
+ appAuthClient = AuthClientFactory.create(
+ metadata: AppMetadata(name: name, description: "", url: "", icons: [""]),
+ projectId: InputConfig.projectId,
+ crypto: DefaultCryptoProvider(),
+ logger: notifyLogger,
+ keyValueStorage: keyValueStorage,
+ keychainStorage: keychain,
+ networkingClient: networkingInteractor,
+ pairingRegisterer: pairingClient,
+ iatProvider: IATProviderMock())
}
func makeWalletPairingClient() {
let prefix = "🐶 Wallet: "
- let (pairingClient, _, _, _, _) = makeClientDependencies(prefix: prefix)
+ let (pairingClient, _, _, _) = makeClientDependencies(prefix: prefix)
walletPairingClient = pairingClient
}
@@ -109,28 +105,11 @@ final class PairingTests: XCTestCase {
makeDappClients()
}
- func testProposePushOnPairing() async {
- makeWalletClients()
- let expectation = expectation(description: "propose push on pairing")
-
- walletPushClient.requestPublisher.sink { _ in
- expectation.fulfill()
- }.store(in: &publishers)
-
- let uri = try! await appPairingClient.create()
-
- try! await walletPairingClient.pair(uri: uri)
-
- try! await appPushClient.propose(account: Account.stub(), topic: uri.topic)
-
- wait(for: [expectation], timeout: InputConfig.defaultTimeout)
- }
-
func testPing() async {
let expectation = expectation(description: "expects ping response")
makeWalletClients()
let uri = try! await appPairingClient.create()
- try! await walletPairingClient.pair(uri: uri)
+ try? await walletPairingClient.pair(uri: uri)
try! await walletPairingClient.ping(topic: uri.topic)
walletPairingClient.pingResponsePublisher
.sink { topic in
@@ -144,16 +123,16 @@ final class PairingTests: XCTestCase {
makeWalletPairingClient()
let expectation = expectation(description: "wallet responds unsupported method for unregistered method")
- appPushClient.proposalResponsePublisher.sink { (response) in
- XCTAssertEqual(response, .failure(PushError(code: 10001)!))
+ appAuthClient.authResponsePublisher.sink { (_, response) in
+ XCTAssertEqual(response, .failure(AuthError(code: 10001)!))
expectation.fulfill()
}.store(in: &publishers)
let uri = try! await appPairingClient.create()
- try! await walletPairingClient.pair(uri: uri)
+ try? await walletPairingClient.pair(uri: uri)
- try! await appPushClient.propose(account: Account.stub(), topic: uri.topic)
+ try! await appAuthClient.request(RequestParams.stub(), topic: uri.topic)
wait(for: [expectation], timeout: InputConfig.defaultTimeout)
}
diff --git a/Example/IntegrationTests/Push/NotifyTests.swift b/Example/IntegrationTests/Push/NotifyTests.swift
new file mode 100644
index 000000000..965c56f5a
--- /dev/null
+++ b/Example/IntegrationTests/Push/NotifyTests.swift
@@ -0,0 +1,176 @@
+import Foundation
+import XCTest
+import WalletConnectUtils
+import Web3
+@testable import WalletConnectKMS
+import WalletConnectRelay
+import Combine
+import WalletConnectNetworking
+import WalletConnectPush
+@testable import WalletConnectNotify
+@testable import WalletConnectPairing
+import WalletConnectIdentity
+import WalletConnectSigner
+
+final class NotifyTests: XCTestCase {
+
+ var walletPairingClient: PairingClient!
+
+ var walletNotifyClient: NotifyClient!
+
+ let gmDappUrl = "https://notify.gm.walletconnect.com/"
+
+ let pk = try! EthereumPrivateKey()
+
+ var privateKey: Data {
+ return Data(pk.rawPrivateKey)
+ }
+
+ var account: Account {
+ return Account("eip155:1:" + pk.address.hex(eip55: true))!
+ }
+
+ private var publishers = Set()
+
+ func makeClientDependencies(prefix: String) -> (PairingClient, NetworkInteracting, KeychainStorageProtocol, KeyValueStorage) {
+ let keychain = KeychainStorageMock()
+ let keyValueStorage = RuntimeKeyValueStorage()
+
+ let relayLogger = ConsoleLogger(prefix: prefix + " [Relay]", loggingLevel: .debug)
+ let pairingLogger = ConsoleLogger(prefix: prefix + " [Pairing]", loggingLevel: .debug)
+ let networkingLogger = ConsoleLogger(prefix: prefix + " [Networking]", loggingLevel: .debug)
+
+ let relayClient = RelayClientFactory.create(
+ relayHost: InputConfig.relayHost,
+ projectId: InputConfig.projectId,
+ keyValueStorage: keyValueStorage,
+ keychainStorage: keychain,
+ socketFactory: DefaultSocketFactory(),
+ logger: relayLogger)
+
+ let networkingClient = NetworkingClientFactory.create(
+ relayClient: relayClient,
+ logger: networkingLogger,
+ keychainStorage: keychain,
+ keyValueStorage: keyValueStorage)
+
+ let pairingClient = PairingClientFactory.create(
+ logger: pairingLogger,
+ keyValueStorage: keyValueStorage,
+ keychainStorage: keychain,
+ networkingClient: networkingClient)
+
+ let clientId = try! networkingClient.getClientId()
+ networkingLogger.debug("My client id is: \(clientId)")
+ return (pairingClient, networkingClient, keychain, keyValueStorage)
+ }
+
+ func makeWalletClients() {
+ let prefix = "🦋 Wallet: "
+ let (pairingClient, networkingInteractor, keychain, keyValueStorage) = makeClientDependencies(prefix: prefix)
+ let notifyLogger = ConsoleLogger(prefix: prefix + " [Notify]", loggingLevel: .debug)
+ walletPairingClient = pairingClient
+ let pushClient = PushClientFactory.create(projectId: "",
+ pushHost: "echo.walletconnect.com",
+ keychainStorage: keychain,
+ environment: .sandbox)
+ let keyserverURL = URL(string: "https://keys.walletconnect.com")!
+ walletNotifyClient = NotifyClientFactory.create(keyserverURL: keyserverURL,
+ logger: notifyLogger,
+ keyValueStorage: keyValueStorage,
+ keychainStorage: keychain,
+ groupKeychainStorage: KeychainStorageMock(),
+ networkInteractor: networkingInteractor,
+ pairingRegisterer: pairingClient,
+ pushClient: pushClient,
+ crypto: DefaultCryptoProvider())
+ }
+
+ override func setUp() {
+ makeWalletClients()
+ }
+
+ func testWalletCreatesSubscription() async {
+ let expectation = expectation(description: "expects to create notify subscription")
+ let metadata = AppMetadata(name: "GM Dapp", description: "", url: gmDappUrl, icons: [])
+
+ walletNotifyClient.newSubscriptionPublisher
+ .sink { [unowned self] subscription in
+ Task(priority: .high) {
+ try! await walletNotifyClient.deleteSubscription(topic: subscription.topic)
+ expectation.fulfill()
+ }
+ }.store(in: &publishers)
+
+ try! await walletNotifyClient.register(account: account, onSign: sign)
+ try! await walletNotifyClient.subscribe(metadata: metadata, account: account, onSign: sign)
+
+ wait(for: [expectation], timeout: InputConfig.defaultTimeout)
+ }
+
+ func testWalletCreatesAndUpdatesSubscription() async {
+ let expectation = expectation(description: "expects to create and update notify subscription")
+ let metadata = AppMetadata(name: "GM Dapp", description: "", url: gmDappUrl, icons: [])
+ let updateScope: Set = ["alerts"]
+ try! await walletNotifyClient.register(account: account, onSign: sign)
+ try! await walletNotifyClient.subscribe(metadata: metadata, account: account, onSign: sign)
+ walletNotifyClient.newSubscriptionPublisher
+ .sink { [unowned self] subscription in
+ Task(priority: .high) {
+ try! await walletNotifyClient.update(topic: subscription.topic, scope: updateScope)
+ }
+ }
+ .store(in: &publishers)
+
+ walletNotifyClient.updateSubscriptionPublisher
+ .sink { [unowned self] subscription in
+ let updatedScope = Set(subscription.scope.filter{ $0.value.enabled == true }.keys)
+ XCTAssertEqual(updatedScope, updateScope)
+ Task(priority: .high) {
+ try! await walletNotifyClient.deleteSubscription(topic: subscription.topic)
+ expectation.fulfill()
+ }
+ }.store(in: &publishers)
+
+ wait(for: [expectation], timeout: InputConfig.defaultTimeout)
+ }
+
+ func testNotifyServerSubscribeAndNotifies() async throws {
+ let subscribeExpectation = expectation(description: "creates notify subscription")
+ let messageExpectation = expectation(description: "receives a notify message")
+ let notifyMessage = NotifyMessage.stub()
+
+ let metadata = AppMetadata(name: "GM Dapp", description: "", url: gmDappUrl, icons: [])
+ try! await walletNotifyClient.register(account: account, onSign: sign)
+ try! await walletNotifyClient.subscribe(metadata: metadata, account: account, onSign: sign)
+
+ walletNotifyClient.newSubscriptionPublisher
+ .sink { subscription in
+ let notifier = Publisher()
+ Task(priority: .high) {
+ try await notifier.notify(topic: subscription.topic, account: subscription.account, message: notifyMessage)
+ subscribeExpectation.fulfill()
+ }
+ }.store(in: &publishers)
+
+ walletNotifyClient.notifyMessagePublisher
+ .sink { [unowned self] notifyMessageRecord in
+ XCTAssertEqual(notifyMessage, notifyMessageRecord.message)
+
+ Task(priority: .high) {
+ try await walletNotifyClient.deleteSubscription(topic: notifyMessageRecord.topic)
+ messageExpectation.fulfill()
+ }
+ }.store(in: &publishers)
+
+ wait(for: [subscribeExpectation, messageExpectation], timeout: InputConfig.defaultTimeout)
+ }
+}
+
+
+private extension NotifyTests {
+ func sign(_ message: String) -> SigningResult {
+ let signer = MessageSignerFactory(signerFactory: DefaultSignerFactory()).create(projectId: InputConfig.projectId)
+ return .signed(try! signer.sign(message: message, privateKey: privateKey, type: .eip191))
+ }
+}
diff --git a/Example/IntegrationTests/Push/Publisher.swift b/Example/IntegrationTests/Push/Publisher.swift
index 50667f1ce..5ee993759 100644
--- a/Example/IntegrationTests/Push/Publisher.swift
+++ b/Example/IntegrationTests/Push/Publisher.swift
@@ -1,8 +1,8 @@
-@testable import WalletConnectPush
+@testable import WalletConnectNotify
import Foundation
class Publisher {
- func notify(topic: String, account: Account, message: PushMessage) async throws {
+ func notify(topic: String, account: Account, message: NotifyMessage) async throws {
let url = URL(string: "https://\(InputConfig.castHost)/\(InputConfig.gmDappProjectId)/notify")!
var request = URLRequest(url: url)
let notifyRequestPayload = NotifyRequest(notification: message, accounts: [account])
@@ -19,6 +19,6 @@ class Publisher {
}
struct NotifyRequest: Codable {
- let notification: PushMessage
+ let notification: NotifyMessage
let accounts: [Account]
}
diff --git a/Example/IntegrationTests/Push/PushTests.swift b/Example/IntegrationTests/Push/PushTests.swift
deleted file mode 100644
index d9a43acf7..000000000
--- a/Example/IntegrationTests/Push/PushTests.swift
+++ /dev/null
@@ -1,271 +0,0 @@
-import Foundation
-import XCTest
-import WalletConnectUtils
-import Web3
-@testable import WalletConnectKMS
-import WalletConnectRelay
-import Combine
-import WalletConnectNetworking
-import WalletConnectEcho
-@testable import WalletConnectPush
-@testable import WalletConnectPairing
-@testable import WalletConnectSync
-@testable import WalletConnectHistory
-import WalletConnectIdentity
-import WalletConnectSigner
-
-final class PushTests: XCTestCase {
-
- var dappPairingClient: PairingClient!
- var walletPairingClient: PairingClient!
-
- var dappPushClient: DappPushClient!
- var walletPushClient: WalletPushClient!
-
- var pairingStorage: PairingStorage!
-
- let pk = try! EthereumPrivateKey()
-
- var privateKey: Data {
- return Data(pk.rawPrivateKey)
- }
-
- var account: Account {
- return Account("eip155:1:" + pk.address.hex(eip55: true))!
- }
-
- private var publishers = [AnyCancellable]()
-
- func makeClientDependencies(prefix: String) -> (PairingClient, NetworkInteracting, SyncClient, KeychainStorageProtocol, KeyValueStorage) {
- let keychain = KeychainStorageMock()
- let keyValueStorage = RuntimeKeyValueStorage()
-
- let relayLogger = ConsoleLogger(suffix: prefix + " [Relay]", loggingLevel: .debug)
- let pairingLogger = ConsoleLogger(suffix: prefix + " [Pairing]", loggingLevel: .debug)
- let networkingLogger = ConsoleLogger(suffix: prefix + " [Networking]", loggingLevel: .debug)
-
- let relayClient = RelayClientFactory.create(
- relayHost: InputConfig.relayHost,
- projectId: InputConfig.projectId,
- keyValueStorage: keyValueStorage,
- keychainStorage: keychain,
- logger: relayLogger)
-
- let networkingClient = NetworkingClientFactory.create(
- relayClient: relayClient,
- logger: networkingLogger,
- keychainStorage: keychain,
- keyValueStorage: keyValueStorage)
-
- let pairingClient = PairingClientFactory.create(
- logger: pairingLogger,
- keyValueStorage: keyValueStorage,
- keychainStorage: keychain,
- networkingClient: networkingClient)
-
- let syncClient = SyncClientFactory.create(networkInteractor: networkingClient, bip44: DefaultBIP44Provider(), keychain: keychain)
-
- let clientId = try! networkingClient.getClientId()
- networkingLogger.debug("My client id is: \(clientId)")
- return (pairingClient, networkingClient, syncClient, keychain, keyValueStorage)
- }
-
- func makeDappClients() {
- let prefix = "🦄 Dapp: "
- let (pairingClient, networkingInteractor, syncClient, keychain, keyValueStorage) = makeClientDependencies(prefix: prefix)
- let pushLogger = ConsoleLogger(suffix: prefix + " [Push]", loggingLevel: .debug)
- dappPairingClient = pairingClient
- dappPushClient = DappPushClientFactory.create(metadata: AppMetadata(name: "GM Dapp", description: "", url: "https://gm-dapp-xi.vercel.app/", icons: []),
- logger: pushLogger,
- keyValueStorage: keyValueStorage,
- keychainStorage: keychain,
- groupKeychainStorage: KeychainStorageMock(),
- networkInteractor: networkingInteractor,
- syncClient: syncClient)
- }
-
- func makeWalletClients() {
- let prefix = "🦋 Wallet: "
- let (pairingClient, networkingInteractor, syncClient, keychain, keyValueStorage) = makeClientDependencies(prefix: prefix)
- let pushLogger = ConsoleLogger(suffix: prefix + " [Push]", loggingLevel: .debug)
- walletPairingClient = pairingClient
- let echoClient = EchoClientFactory.create(projectId: "",
- echoHost: "echo.walletconnect.com",
- keychainStorage: keychain,
- environment: .sandbox)
- let keyserverURL = URL(string: "https://keys.walletconnect.com")!
- let historyClient = HistoryClientFactory.create(
- historyUrl: "https://history.walletconnect.com",
- relayUrl: "wss://relay.walletconnect.com",
- keychain: keychain
- )
- walletPushClient = WalletPushClientFactory.create(keyserverURL: keyserverURL,
- logger: pushLogger,
- keyValueStorage: keyValueStorage,
- keychainStorage: keychain,
- groupKeychainStorage: KeychainStorageMock(),
- networkInteractor: networkingInteractor,
- pairingRegisterer: pairingClient,
- echoClient: echoClient,
- syncClient: syncClient,
- historyClient: historyClient)
- }
-
- override func setUp() {
- makeDappClients()
- makeWalletClients()
- }
-
- func testPushPropose() async {
- let expectation = expectation(description: "expects dapp to receive error response")
-
- let uri = try! await dappPairingClient.create()
- try! await walletPairingClient.pair(uri: uri)
- try! await walletPushClient.enableSync(account: account, onSign: sign)
- try! await dappPushClient.propose(account: account, topic: uri.topic)
-
- walletPushClient.requestPublisher.sink { [unowned self] (id, _, _) in
- Task(priority: .high) { try! await walletPushClient.approve(id: id, onSign: sign) }
- }.store(in: &publishers)
-
- dappPushClient.proposalResponsePublisher.sink { (result) in
- guard case .success = result else {
- XCTFail()
- return
- }
- expectation.fulfill()
- }.store(in: &publishers)
- wait(for: [expectation], timeout: InputConfig.defaultTimeout)
-
- }
-
- func testWalletRejectsPushPropose() async {
- let expectation = expectation(description: "expects dapp to receive error response")
-
- let uri = try! await dappPairingClient.create()
- try! await walletPairingClient.pair(uri: uri)
- try! await dappPushClient.propose(account: account, topic: uri.topic)
-
- walletPushClient.requestPublisher.sink { [unowned self] (id, _, _) in
- Task(priority: .high) { try! await walletPushClient.reject(id: id) }
- }.store(in: &publishers)
-
- dappPushClient.proposalResponsePublisher.sink { (result) in
- guard case .failure = result else {
- XCTFail()
- return
- }
- expectation.fulfill()
- }.store(in: &publishers)
-
- wait(for: [expectation], timeout: InputConfig.defaultTimeout)
- }
-
- func testWalletCreatesSubscription() async {
- let expectation = expectation(description: "expects to create push subscription")
- let metadata = AppMetadata(name: "GM Dapp", description: "", url: "https://gm-dapp-xi.vercel.app/", icons: [])
- try! await walletPushClient.enableSync(account: account, onSign: sign)
- try! await walletPushClient.subscribe(metadata: metadata, account: account, onSign: sign)
- walletPushClient.subscriptionsPublisher
- .first()
- .sink { [unowned self] subscriptions in
- XCTAssertNotNil(subscriptions.first)
- Task { try! await walletPushClient.deleteSubscription(topic: subscriptions.first!.topic) }
- expectation.fulfill()
- }.store(in: &publishers)
- wait(for: [expectation], timeout: InputConfig.defaultTimeout)
- }
-
- func testDeletePushSubscription() async {
- let expectation = expectation(description: "expects to delete push subscription")
- let uri = try! await dappPairingClient.create()
- try! await walletPairingClient.pair(uri: uri)
- try! await walletPushClient.enableSync(account: account, onSign: sign)
- try! await dappPushClient.propose(account: account, topic: uri.topic)
- var subscriptionTopic: String!
-
- walletPushClient.requestPublisher.sink { [unowned self] (id, _, _) in
- Task(priority: .high) { try! await walletPushClient.approve(id: id, onSign: sign) }
- }.store(in: &publishers)
-
- dappPushClient.proposalResponsePublisher.sink { [unowned self] (result) in
- guard case .success(let pushSubscription) = result else {
- XCTFail()
- return
- }
- subscriptionTopic = pushSubscription.topic
- sleep(1)
- Task(priority: .userInitiated) { try! await walletPushClient.deleteSubscription(topic: pushSubscription.topic)}
- }.store(in: &publishers)
-
- dappPushClient.deleteSubscriptionPublisher.sink { topic in
- XCTAssertEqual(subscriptionTopic, topic)
- expectation.fulfill()
- }.store(in: &publishers)
- wait(for: [expectation], timeout: InputConfig.defaultTimeout)
- }
-
- func testWalletCreatesAndUpdatesSubscription() async {
- let expectation = expectation(description: "expects to create and update push subscription")
- let metadata = AppMetadata(name: "GM Dapp", description: "", url: "https://gm-dapp-xi.vercel.app/", icons: [])
- let updateScope: Set = ["alerts"]
- try! await walletPushClient.enableSync(account: account, onSign: sign)
- try! await walletPushClient.subscribe(metadata: metadata, account: account, onSign: sign)
- walletPushClient.subscriptionsPublisher
- .first()
- .sink { [unowned self] subscriptions in
- sleep(1)
- Task { try! await walletPushClient.update(topic: subscriptions.first!.topic, scope: updateScope) }
- }
- .store(in: &publishers)
-
- walletPushClient.updateSubscriptionPublisher
- .sink { [unowned self] result in
- guard case .success(let subscription) = result else { XCTFail(); return }
- let updatedScope = Set(subscription.scope.filter{ $0.value.enabled == true }.keys)
- XCTAssertEqual(updatedScope, updateScope)
- Task { try! await walletPushClient.deleteSubscription(topic: subscription.topic) }
- expectation.fulfill()
- }.store(in: &publishers)
-
- wait(for: [expectation], timeout: InputConfig.defaultTimeout)
- }
-
- func testNotifyServerSubscribeAndNotifies() async throws {
- let subscribeExpectation = expectation(description: "creates push subscription")
- let messageExpectation = expectation(description: "receives a push message")
- let pushMessage = PushMessage.stub()
-
- let metadata = AppMetadata(name: "GM Dapp", description: "", url: "https://gm-dapp-xi.vercel.app/", icons: [])
- try! await walletPushClient.enableSync(account: account, onSign: sign)
- try! await walletPushClient.subscribe(metadata: metadata, account: account, onSign: sign)
- var subscription: PushSubscription!
- walletPushClient.subscriptionsPublisher
- .first()
- .sink { subscriptions in
- XCTAssertNotNil(subscriptions.first)
- subscribeExpectation.fulfill()
- subscription = subscriptions.first!
- let notifier = Publisher()
- sleep(1)
- Task(priority: .high) { try await notifier.notify(topic: subscriptions.first!.topic, account: subscriptions.first!.account, message: pushMessage) }
- }.store(in: &publishers)
- walletPushClient.pushMessagePublisher
- .sink { pushMessageRecord in
- XCTAssertEqual(pushMessage, pushMessageRecord.message)
- messageExpectation.fulfill()
- }.store(in: &publishers)
-
- wait(for: [subscribeExpectation, messageExpectation], timeout: InputConfig.defaultTimeout)
- try await walletPushClient.deleteSubscription(topic: subscription.topic)
- }
-
-}
-
-
-private extension PushTests {
- func sign(_ message: String) -> SigningResult {
- let signer = MessageSignerFactory(signerFactory: DefaultSignerFactory()).create(projectId: InputConfig.projectId)
- return .signed(try! signer.sign(message: message, privateKey: privateKey, type: .eip191))
- }
-}
diff --git a/Example/IntegrationTests/Sign/SignClientTests.swift b/Example/IntegrationTests/Sign/SignClientTests.swift
index da4e82373..256c9e92b 100644
--- a/Example/IntegrationTests/Sign/SignClientTests.swift
+++ b/Example/IntegrationTests/Sign/SignClientTests.swift
@@ -12,7 +12,7 @@ final class SignClientTests: XCTestCase {
var wallet: ClientDelegate!
static private func makeClientDelegate(name: String) -> ClientDelegate {
- let logger = ConsoleLogger(suffix: name, loggingLevel: .debug)
+ let logger = ConsoleLogger(prefix: name, loggingLevel: .debug)
let keychain = KeychainStorageMock()
let keyValueStorage = RuntimeKeyValueStorage()
let relayClient = RelayClientFactory.create(
@@ -433,7 +433,7 @@ final class SignClientTests: XCTestCase {
let sessionProposal = Session.Proposal(
id: "",
pairingTopic: "",
- proposer: AppMetadata(name: "", description: "", url: "", icons: []),
+ proposer: AppMetadata.stub(),
requiredNamespaces: requiredNamespaces,
optionalNamespaces: optionalNamespaces,
sessionProperties: nil,
diff --git a/Example/IntegrationTests/Stubs/PushMessage.swift b/Example/IntegrationTests/Stubs/PushMessage.swift
index 5355a3aeb..1ad6880ee 100644
--- a/Example/IntegrationTests/Stubs/PushMessage.swift
+++ b/Example/IntegrationTests/Stubs/PushMessage.swift
@@ -1,9 +1,9 @@
import Foundation
-import WalletConnectPush
+import WalletConnectNotify
-extension PushMessage {
- static func stub() -> PushMessage {
- return PushMessage(
+extension NotifyMessage {
+ static func stub() -> NotifyMessage {
+ return NotifyMessage(
title: "swift_test",
body: "gm_hourly",
icon: "https://images.unsplash.com/photo-1581224463294-908316338239?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=250&q=80",
diff --git a/Example/IntegrationTests/Stubs/Stubs.swift b/Example/IntegrationTests/Stubs/Stubs.swift
index b2aa32f53..8b815a474 100644
--- a/Example/IntegrationTests/Stubs/Stubs.swift
+++ b/Example/IntegrationTests/Stubs/Stubs.swift
@@ -22,3 +22,14 @@ extension SessionNamespace {
}
}
}
+
+extension AppMetadata {
+ static func stub() -> AppMetadata {
+ return AppMetadata(
+ name: "WalletConnectSwift",
+ description: "WalletConnectSwift",
+ url: "https://walletconnect.com",
+ icons: []
+ )
+ }
+}
diff --git a/Example/IntegrationTests/Sync/SyncTests.swift b/Example/IntegrationTests/Sync/SyncTests.swift
index f5e4a4088..adcfdc532 100644
--- a/Example/IntegrationTests/Sync/SyncTests.swift
+++ b/Example/IntegrationTests/Sync/SyncTests.swift
@@ -56,9 +56,14 @@ final class SyncTests: XCTestCase {
let keychain = KeychainStorageMock()
let kms = KeyManagementService(keychain: keychain)
let derivationService = SyncDerivationService(syncStorage: syncSignatureStore, bip44: DefaultBIP44Provider(), kms: kms)
- let logger = ConsoleLogger(suffix: suffix, loggingLevel: .debug)
- let relayClient = RelayClientFactory.create(relayHost: InputConfig.relayHost, projectId: InputConfig.projectId, keyValueStorage: RuntimeKeyValueStorage(), keychainStorage: keychain, logger: logger)
-
+ let logger = ConsoleLogger(prefix: suffix, loggingLevel: .debug)
+ let relayClient = RelayClientFactory.create(
+ relayHost: InputConfig.relayHost,
+ projectId: InputConfig.projectId,
+ keyValueStorage: RuntimeKeyValueStorage(),
+ keychainStorage: keychain,
+ socketFactory: DefaultSocketFactory(),
+ logger: logger)
let networkingInteractor = NetworkingClientFactory.create(
relayClient: relayClient,
logger: logger,
@@ -83,8 +88,10 @@ final class SyncTests: XCTestCase {
func testSync() async throws {
let setExpectation = expectation(description: "syncSetTest")
let delExpectation = expectation(description: "syncDelTest")
+ let uptExpectation = expectation(description: "syncUptTest")
let object = TestObject(id: "id-1", value: "value-1")
+ let updated = TestObject(id: "id-1", value: "value-2")
syncStore1.syncUpdatePublisher.sink { (_, _, update) in
switch update {
@@ -92,6 +99,8 @@ final class SyncTests: XCTestCase {
XCTFail()
case .delete:
delExpectation.fulfill()
+ case .update:
+ XCTFail()
}
}.store(in: &publishers)
@@ -101,6 +110,8 @@ final class SyncTests: XCTestCase {
setExpectation.fulfill()
case .delete:
XCTFail()
+ case .update:
+ uptExpectation.fulfill()
}
}.store(in: &publishers)
@@ -118,6 +129,15 @@ final class SyncTests: XCTestCase {
XCTAssertEqual(try syncStore1.getAll(for: account), [object])
XCTAssertEqual(try syncStore2.getAll(for: account), [object])
+ // Testing SyncStore `update`
+
+ try await syncStore1.set(object: updated, for: account)
+
+ wait(for: [uptExpectation], timeout: InputConfig.defaultTimeout)
+
+ XCTAssertEqual(try syncStore1.getAll(for: account), [updated])
+ XCTAssertEqual(try syncStore2.getAll(for: account), [updated])
+
// Testing SyncStore `delete`
try await syncStore2.delete(id: object.id, for: account)
@@ -135,5 +155,6 @@ final class SyncTests: XCTestCase {
try await client.register(account: account, signature: signature)
try await client.create(account: account, store: storeName)
+ try await client.subscribe(account: account, store: storeName)
}
}
diff --git a/Example/IntegrationTests/XPlatform/Web3Wallet/XPlatformW3WTests.swift b/Example/IntegrationTests/XPlatform/Web3Wallet/XPlatformW3WTests.swift
new file mode 100644
index 000000000..e08391021
--- /dev/null
+++ b/Example/IntegrationTests/XPlatform/Web3Wallet/XPlatformW3WTests.swift
@@ -0,0 +1,144 @@
+import Foundation
+import XCTest
+import Combine
+@testable import Web3Wallet
+@testable import Auth
+@testable import WalletConnectSign
+@testable import WalletConnectPush
+
+final class XPlatformW3WTests: XCTestCase {
+ var w3wClient: Web3WalletClient!
+ var javaScriptAutoTestsAPI: JavaScriptAutoTestsAPI!
+ private var publishers = [AnyCancellable]()
+
+ override func setUp() {
+ makeClient()
+ javaScriptAutoTestsAPI = JavaScriptAutoTestsAPI()
+ }
+
+ func makeClient() {
+ let keychain = KeychainStorageMock()
+ let keyValueStorage = RuntimeKeyValueStorage()
+
+ let relayLogger = ConsoleLogger(prefix: "🚄" + " [Relay]", loggingLevel: .debug)
+ let pairingLogger = ConsoleLogger(prefix: "👩❤️💋👩" + " [Pairing]", loggingLevel: .debug)
+ let networkingLogger = ConsoleLogger(prefix: "🕸️" + " [Networking]", loggingLevel: .debug)
+ let authLogger = ConsoleLogger(prefix: "🪪", loggingLevel: .debug)
+
+ let signLogger = ConsoleLogger(prefix: "✍🏿", loggingLevel: .debug)
+
+ let relayClient = RelayClientFactory.create(
+ relayHost: InputConfig.relayHost,
+ projectId: InputConfig.projectId,
+ keyValueStorage: keyValueStorage,
+ keychainStorage: keychain,
+ socketFactory: DefaultSocketFactory(),
+ logger: relayLogger
+ )
+
+ let networkingClient = NetworkingClientFactory.create(
+ relayClient: relayClient,
+ logger: networkingLogger,
+ keychainStorage: keychain,
+ keyValueStorage: keyValueStorage)
+
+ let pairingClient = PairingClientFactory.create(
+ logger: pairingLogger,
+ keyValueStorage: keyValueStorage,
+ keychainStorage: keychain,
+ networkingClient: networkingClient)
+
+ let signClient = SignClientFactory.create(
+ metadata: AppMetadata(name: name, description: "", url: "", icons: [""]),
+ logger: signLogger,
+ keyValueStorage: keyValueStorage,
+ keychainStorage: keychain,
+ pairingClient: pairingClient,
+ networkingClient: networkingClient
+ )
+
+ let authClient = AuthClientFactory.create(
+ metadata: AppMetadata(name: name, description: "", url: "", icons: [""]),
+ projectId: InputConfig.projectId,
+ crypto: DefaultCryptoProvider(),
+ logger: authLogger,
+ keyValueStorage: keyValueStorage,
+ keychainStorage: keychain,
+ networkingClient: networkingClient,
+ pairingRegisterer: pairingClient,
+ iatProvider: DefaultIATProvider())
+
+ w3wClient = Web3WalletClientFactory.create(
+ authClient: authClient,
+ signClient: signClient,
+ pairingClient: pairingClient,
+ pushClient: PushClientMock())
+ }
+
+ func testSessionSettle() async throws {
+
+ let expectation = expectation(description: "session settled")
+
+ w3wClient.sessionProposalPublisher
+ .sink { [unowned self] (proposal, _) in
+ Task(priority: .high) {
+ let sessionNamespaces = SessionNamespace.make(toRespond: proposal.requiredNamespaces)
+ try await w3wClient.approve(proposalId: proposal.id, namespaces: sessionNamespaces)
+ }
+ }
+ .store(in: &publishers)
+
+ w3wClient.sessionSettlePublisher.sink { [unowned self] session in
+ Task {
+ var jsSession: JavaScriptAutoTestsAPI.Session?
+
+ while jsSession == nil {
+ print("🎃 geting session")
+ do {
+ jsSession = try await javaScriptAutoTestsAPI.getSession(topic: session.topic)
+ } catch {
+ print("No session on JS client yet")
+ }
+
+ if jsSession == nil {
+ sleep(1)
+ }
+ }
+
+ XCTAssertEqual(jsSession?.topic, session.topic)
+ expectation.fulfill()
+ }
+ }
+ .store(in: &publishers)
+
+ let pairingUri = try await javaScriptAutoTestsAPI.quickConnect()
+ try await w3wClient.pair(uri: pairingUri)
+
+ wait(for: [expectation], timeout: InputConfig.defaultTimeout)
+ }
+
+}
+
+
+class JavaScriptAutoTestsAPI {
+ private let host = "https://\(InputConfig.jsClientApiHost)"
+
+ func quickConnect() async throws -> WalletConnectURI {
+ let url = URL(string: "\(host)/quick_connect")!
+ let (data, _) = try await URLSession.shared.data(from: url)
+ let uriString = String(decoding: data, as: UTF8.self)
+ return WalletConnectURI(string: uriString)!
+ }
+
+ func getSession(topic: String) async throws -> Session {
+ let url = URL(string: "\(host)/session/\(topic)")!
+ let (data, _) = try await URLSession.shared.data(from: url)
+ return try JSONDecoder().decode(Session.self, from: data)
+ }
+
+ // Testing Data Structures to match JS responses
+
+ struct Session: Decodable {
+ let topic: String
+ }
+}
diff --git a/Example/PNDecryptionService/NotificationService.swift b/Example/PNDecryptionService/NotificationService.swift
index 1be5f2335..abf6473c2 100644
--- a/Example/PNDecryptionService/NotificationService.swift
+++ b/Example/PNDecryptionService/NotificationService.swift
@@ -1,5 +1,5 @@
import UserNotifications
-import WalletConnectPush
+import WalletConnectNotify
import os
class NotificationService: UNNotificationServiceExtension {
@@ -13,9 +13,9 @@ class NotificationService: UNNotificationServiceExtension {
if let bestAttemptContent = bestAttemptContent {
let topic = bestAttemptContent.userInfo["topic"] as! String
let ciphertext = bestAttemptContent.userInfo["blob"] as! String
- NSLog("echo decryption, topic=%@", topic)
+ NSLog("Push decryption, topic=%@", topic)
do {
- let service = PushDecryptionService()
+ let service = NotifyDecryptionService()
let pushMessage = try service.decryptMessage(topic: topic, ciphertext: ciphertext)
bestAttemptContent.title = pushMessage.title
bestAttemptContent.body = pushMessage.body
@@ -23,7 +23,7 @@ class NotificationService: UNNotificationServiceExtension {
return
}
catch {
- NSLog("echo decryption, error=%@", error.localizedDescription)
+ NSLog("Push decryption, error=%@", error.localizedDescription)
bestAttemptContent.title = ""
bestAttemptContent.body = "content not set"
}
diff --git a/Example/RelayIntegrationTests/RelayClientEndToEndTests.swift b/Example/RelayIntegrationTests/RelayClientEndToEndTests.swift
index 8bc77ffec..2f584b3b3 100644
--- a/Example/RelayIntegrationTests/RelayClientEndToEndTests.swift
+++ b/Example/RelayIntegrationTests/RelayClientEndToEndTests.swift
@@ -40,10 +40,9 @@ final class RelayClientEndToEndTests: XCTestCase {
projectId: InputConfig.projectId,
socketAuthenticator: socketAuthenticator
)
- let logger = ConsoleLogger(suffix: prefix, loggingLevel: .debug)
- let socket = WebSocketClient(url: urlFactory.create(fallback: false), logger: ConsoleLogger(suffix: prefix, loggingLevel: .debug))
+ let socket = WebSocket(url: urlFactory.create(fallback: false))
let webSocketFactory = WebSocketFactoryMock(webSocket: socket)
-
+ let logger = ConsoleLogger(prefix: prefix, loggingLevel: .debug)
let dispatcher = Dispatcher(
socketFactory: webSocketFactory,
relayUrlFactory: urlFactory,
diff --git a/Example/Shared/DefaultCryptoProvider.swift b/Example/Shared/DefaultCryptoProvider.swift
index 4905855f7..3baf2b7e9 100644
--- a/Example/Shared/DefaultCryptoProvider.swift
+++ b/Example/Shared/DefaultCryptoProvider.swift
@@ -22,20 +22,4 @@ struct DefaultCryptoProvider: CryptoProvider {
return Data(hash)
}
- public func derive(entropy: Data, path: [WalletConnectSigner.DerivationPath]) -> Data {
- let mnemonic = Mnemonic.create(entropy: entropy)
- let seed = Mnemonic.createSeed(mnemonic: mnemonic)
- let privateKey = PrivateKey(seed: seed, coin: .bitcoin)
-
- let derived = path.reduce(privateKey) { result, path in
- switch path {
- case .hardened(let index):
- return result.derived(at: .hardened(index))
- case .notHardened(let index):
- return result.derived(at: .notHardened(index))
- }
- }
-
- return derived.raw
- }
}
diff --git a/Example/Shared/Tests/InputConfig.swift b/Example/Shared/Tests/InputConfig.swift
index 8ffbb8770..e32975741 100644
--- a/Example/Shared/Tests/InputConfig.swift
+++ b/Example/Shared/Tests/InputConfig.swift
@@ -18,6 +18,10 @@ struct InputConfig {
return config(for: "GM_DAPP_PROJECT_SECRET")!
}
+ static var jsClientApiHost: String {
+ return config(for: "JS_CLIENT_API_HOST")!
+ }
+
static var relayUrl: String {
return "wss://\(relayHost)"
}
diff --git a/Example/Showcase/Classes/PresentationLayer/Web3Inbox/Web3InboxViewController.swift b/Example/Showcase/Classes/PresentationLayer/Web3Inbox/Web3InboxViewController.swift
index 4a7a87c8f..c6a2d32d6 100644
--- a/Example/Showcase/Classes/PresentationLayer/Web3Inbox/Web3InboxViewController.swift
+++ b/Example/Showcase/Classes/PresentationLayer/Web3Inbox/Web3InboxViewController.swift
@@ -18,7 +18,7 @@ final class Web3InboxViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
- Web3Inbox.configure(account: importAccount.account, bip44: DefaultBIP44Provider(), config: [.pushEnabled: false], environment: .sandbox, onSign: onSing)
+ Web3Inbox.configure(account: importAccount.account, bip44: DefaultBIP44Provider(), config: [.notifyEnabled: false], environment: .sandbox, crypto: DefaultCryptoProvider(), onSign: onSing)
edgesForExtendedLayout = []
navigationItem.title = "Web3Inbox SDK"
diff --git a/Example/WalletApp/ApplicationLayer/Application.swift b/Example/WalletApp/ApplicationLayer/Application.swift
index c1b3499dc..4015be2e3 100644
--- a/Example/WalletApp/ApplicationLayer/Application.swift
+++ b/Example/WalletApp/ApplicationLayer/Application.swift
@@ -11,3 +11,4 @@ final class Application {
lazy var messageSigner = MessageSignerFactory(signerFactory: DefaultSignerFactory()).create()
lazy var configurationService = ConfigurationService()
}
+
diff --git a/Example/WalletApp/ApplicationLayer/ConfigurationService.swift b/Example/WalletApp/ApplicationLayer/ConfigurationService.swift
index 2686adab6..919d3292b 100644
--- a/Example/WalletApp/ApplicationLayer/ConfigurationService.swift
+++ b/Example/WalletApp/ApplicationLayer/ConfigurationService.swift
@@ -5,12 +5,32 @@ import Web3Inbox
final class ConfigurationService {
func configure(importAccount: ImportAccount) {
+ Networking.configure(projectId: InputConfig.projectId, socketFactory: DefaultSocketFactory())
+ Networking.instance.setLogging(level: .debug)
+
+ let metadata = AppMetadata(
+ name: "Example Wallet",
+ description: "wallet description",
+ url: "example.wallet",
+ icons: ["https://avatars.githubusercontent.com/u/37784886"]
+ )
+
+ Web3Wallet.configure(metadata: metadata, crypto: DefaultCryptoProvider(), environment: BuildConfiguration.shared.apnsEnvironment)
+
Web3Inbox.configure(
account: importAccount.account,
bip44: DefaultBIP44Provider(),
config: [.chatEnabled: false, .settingsEnabled: false],
environment: BuildConfiguration.shared.apnsEnvironment,
+ crypto: DefaultCryptoProvider(),
onSign: importAccount.onSign
)
+ Web3Inbox.instance.setLogging(level: .debug)
+
+ if let clientId = try? Networking.interactor.getClientId() {
+ LoggingService.instance.setUpUser(account: importAccount.account.absoluteString, clientId: clientId)
+ ProfilingService.instance.setUpProfiling(account: importAccount.account.absoluteString, clientId: clientId)
+ }
+ LoggingService.instance.startLogging()
}
}
diff --git a/Example/WalletApp/ApplicationLayer/Configurator/ThirdPartyConfigurator.swift b/Example/WalletApp/ApplicationLayer/Configurator/ThirdPartyConfigurator.swift
index 6770fd604..6669e9637 100644
--- a/Example/WalletApp/ApplicationLayer/Configurator/ThirdPartyConfigurator.swift
+++ b/Example/WalletApp/ApplicationLayer/Configurator/ThirdPartyConfigurator.swift
@@ -6,15 +6,10 @@ import Web3Wallet
struct ThirdPartyConfigurator: Configurator {
func configure() {
- Networking.configure(projectId: InputConfig.projectId, socketFactory: DefaultSocketFactory())
- let metadata = AppMetadata(
- name: "Example Wallet",
- description: "wallet description",
- url: "example.wallet",
- icons: ["https://avatars.githubusercontent.com/u/37784886"]
- )
+ }
- Web3Wallet.configure(metadata: metadata, crypto: DefaultCryptoProvider(), environment: BuildConfiguration.shared.apnsEnvironment)
+ private func configureLogging() {
+ LoggingService.instance.configure()
}
}
diff --git a/Example/WalletApp/ApplicationLayer/LoggingService.swift b/Example/WalletApp/ApplicationLayer/LoggingService.swift
new file mode 100644
index 000000000..e7a74cb9b
--- /dev/null
+++ b/Example/WalletApp/ApplicationLayer/LoggingService.swift
@@ -0,0 +1,62 @@
+import Foundation
+import Combine
+import Sentry
+import WalletConnectNetworking
+
+final class LoggingService {
+ enum LoggingError: Error {
+ case networking(String)
+ }
+
+ public static var instance = LoggingService()
+ private var publishers = [AnyCancellable]()
+ private var isLogging: Bool {
+ get {
+ return queue.sync { _isLogging }
+ }
+ set {
+ queue.sync { _isLogging = newValue }
+ }
+ }
+ private var _isLogging = false
+
+ private let queue = DispatchQueue(label: "com.walletApp.loggingService")
+
+ func setUpUser(account: String, clientId: String) {
+ let user = User()
+ user.userId = clientId
+ user.data = ["account": account]
+ SentrySDK.setUser(user)
+ }
+
+ func configure() {
+ guard let sentryDsn = InputConfig.sentryDsn, !sentryDsn.isEmpty else { return }
+ SentrySDK.start { options in
+ options.dsn = "https://\(sentryDsn)"
+ options.tracesSampleRate = 1.0
+ }
+ }
+
+ func startLogging() {
+ guard !isLogging else { return }
+ isLogging = true
+
+ Networking.instance.logsPublisher
+ .sink { [weak self] log in
+ self?.queue.sync {
+ switch log {
+ case .error(let log):
+ SentrySDK.capture(error: LoggingError.networking(log.aggregated))
+ case .warn(let log):
+ // Example of setting level to warning
+ var event = Event(level: .warning)
+ event.message = SentryMessage(formatted: log.aggregated)
+ SentrySDK.capture(event: event)
+ default:
+ return
+ }
+ }
+ }
+ .store(in: &publishers)
+ }
+}
diff --git a/Example/WalletApp/ApplicationLayer/ProfilingService.swift b/Example/WalletApp/ApplicationLayer/ProfilingService.swift
new file mode 100644
index 000000000..14a778061
--- /dev/null
+++ b/Example/WalletApp/ApplicationLayer/ProfilingService.swift
@@ -0,0 +1,58 @@
+import Foundation
+import Mixpanel
+import WalletConnectNetworking
+import Combine
+import Web3Inbox
+
+final class ProfilingService {
+ public static var instance = ProfilingService()
+
+ private let queue = DispatchQueue(label: "com.walletApp.profilingService")
+ private var publishers = [AnyCancellable]()
+ private var isProfiling: Bool {
+ get {
+ return queue.sync { _isProfiling }
+ }
+ set {
+ queue.sync { _isProfiling = newValue }
+ }
+ }
+ private var _isProfiling = false
+
+ func setUpProfiling(account: String, clientId: String) {
+ guard !isProfiling else { return }
+ isProfiling = true
+
+ guard let token = InputConfig.mixpanelToken, !token.isEmpty else { return }
+
+ Mixpanel.initialize(token: token, trackAutomaticEvents: true)
+ let mixpanel = Mixpanel.mainInstance()
+ mixpanel.alias = account
+ mixpanel.identify(distinctId: clientId)
+ mixpanel.people.set(properties: ["$name": account, "account": account])
+
+ handleLogs(from: Networking.instance.logsPublisher)
+ handleLogs(from: Web3Inbox.instance.logsPublisher)
+ }
+
+ private func handleLogs(from publisher: AnyPublisher) {
+ publisher
+ .sink { [unowned self] log in
+ self.queue.sync {
+ switch log {
+ case .error(let logMessage),
+ .warn(let logMessage),
+ .debug(let logMessage):
+ self.send(logMessage: logMessage)
+ default:
+ return
+ }
+ }
+ }
+ .store(in: &publishers)
+ }
+
+ func send(logMessage: LogMessage) {
+ Mixpanel.mainInstance().track(event: logMessage.message, properties: logMessage.properties)
+ }
+}
diff --git a/Example/WalletApp/ApplicationLayer/PushRegisterer.swift b/Example/WalletApp/ApplicationLayer/PushRegisterer.swift
index 91a7d63d5..b3f02f00d 100644
--- a/Example/WalletApp/ApplicationLayer/PushRegisterer.swift
+++ b/Example/WalletApp/ApplicationLayer/PushRegisterer.swift
@@ -1,12 +1,9 @@
-
-import WalletConnectPush
+import WalletConnectNotify
import Combine
import UIKit
class PushRegisterer {
- private var publishers = [AnyCancellable]()
-
func getNotificationSettings() {
UNUserNotificationCenter.current().getNotificationSettings { settings in
print("Notification settings: \(settings)")
diff --git a/Example/WalletApp/Common/BuildConfiguration.swift b/Example/WalletApp/Common/BuildConfiguration.swift
index f17d08e73..4e70cc13f 100644
--- a/Example/WalletApp/Common/BuildConfiguration.swift
+++ b/Example/WalletApp/Common/BuildConfiguration.swift
@@ -1,5 +1,5 @@
import Foundation
-import WalletConnectPush
+import WalletConnectNotify
class BuildConfiguration {
enum Environment: String {
diff --git a/Example/WalletApp/Common/InputConfig.swift b/Example/WalletApp/Common/InputConfig.swift
index 944f6a840..b8ce51193 100644
--- a/Example/WalletApp/Common/InputConfig.swift
+++ b/Example/WalletApp/Common/InputConfig.swift
@@ -8,6 +8,14 @@ struct InputConfig {
return projectId
}
+
+ static var sentryDsn: String? {
+ return config(for: "WALLETAPP_SENTRY_DSN")
+ }
+
+ static var mixpanelToken: String? {
+ return config(for: "MIXPANEL_TOKEN")
+ }
private static func config(for key: String) -> String? {
return Bundle.main.object(forInfoDictionaryKey: key) as? String
diff --git a/Example/WalletApp/Other/Info.plist b/Example/WalletApp/Other/Info.plist
index f8d34de93..2f0f15d26 100644
--- a/Example/WalletApp/Other/Info.plist
+++ b/Example/WalletApp/Other/Info.plist
@@ -26,8 +26,12 @@
PROJECT_ID
$(PROJECT_ID)
+ WALLETAPP_SENTRY_DSN
+ $(WALLETAPP_SENTRY_DSN)
SIMULATOR_IDENTIFIER
$(SIMULATOR_IDENTIFIER)
+ MIXPANEL_TOKEN
+ $(MIXPANEL_TOKEN)
UIApplicationSceneManifest
UIApplicationSupportsMultipleScenes
diff --git a/Example/WalletApp/PresentationLayer/Wallet/Main/MainInteractor.swift b/Example/WalletApp/PresentationLayer/Wallet/Main/MainInteractor.swift
index 150fe3ad6..ecae96621 100644
--- a/Example/WalletApp/PresentationLayer/Wallet/Main/MainInteractor.swift
+++ b/Example/WalletApp/PresentationLayer/Wallet/Main/MainInteractor.swift
@@ -2,10 +2,19 @@ import Foundation
import Combine
import Web3Wallet
-import WalletConnectPush
+import WalletConnectNotify
final class MainInteractor {
- var pushRequestPublisher: AnyPublisher<(id: RPCID, account: Account, metadata: AppMetadata), Never> {
- return Push.wallet.requestPublisher
+
+ var sessionProposalPublisher: AnyPublisher<(proposal: Session.Proposal, context: VerifyContext?), Never> {
+ return Web3Wallet.instance.sessionProposalPublisher
+ }
+
+ var sessionRequestPublisher: AnyPublisher<(request: Request, context: VerifyContext?), Never> {
+ return Web3Wallet.instance.sessionRequestPublisher
+ }
+
+ var requestPublisher: AnyPublisher<(request: AuthRequest, context: VerifyContext?), Never> {
+ return Web3Wallet.instance.authRequestPublisher
}
}
diff --git a/Example/WalletApp/PresentationLayer/Wallet/Main/MainPresenter.swift b/Example/WalletApp/PresentationLayer/Wallet/Main/MainPresenter.swift
index dc8a74069..4be0dc58a 100644
--- a/Example/WalletApp/PresentationLayer/Wallet/Main/MainPresenter.swift
+++ b/Example/WalletApp/PresentationLayer/Wallet/Main/MainPresenter.swift
@@ -16,6 +16,7 @@ final class MainPresenter {
var viewControllers: [UIViewController] {
return [
router.walletViewController(importAccount: importAccount),
+ router.notificationsViewController(),
router.web3InboxViewController(),
router.settingsViewController()
]
@@ -35,15 +36,28 @@ final class MainPresenter {
// MARK: - Private functions
extension MainPresenter {
-
private func setupInitialState() {
configurationService.configure(importAccount: importAccount)
pushRegisterer.registerForPushNotifications()
- interactor.pushRequestPublisher
+ interactor.sessionProposalPublisher
+ .receive(on: DispatchQueue.main)
+ .sink { [unowned self] session in
+ router.present(proposal: session.proposal, importAccount: importAccount, context: session.context)
+ }
+ .store(in: &disposeBag)
+
+ interactor.sessionRequestPublisher
.receive(on: DispatchQueue.main)
- .sink { [unowned self] request in
- router.present(pushRequest: request)
+ .sink { [unowned self] request, context in
+ router.present(sessionRequest: request, importAccount: importAccount, sessionContext: context)
}.store(in: &disposeBag)
+
+ interactor.requestPublisher
+ .receive(on: DispatchQueue.main)
+ .sink { [unowned self] result in
+ router.present(request: result.request, importAccount: importAccount, context: result.context)
+ }
+ .store(in: &disposeBag)
}
}
diff --git a/Example/WalletApp/PresentationLayer/Wallet/Main/MainRouter.swift b/Example/WalletApp/PresentationLayer/Wallet/Main/MainRouter.swift
index e78a40f6d..7d176926f 100644
--- a/Example/WalletApp/PresentationLayer/Wallet/Main/MainRouter.swift
+++ b/Example/WalletApp/PresentationLayer/Wallet/Main/MainRouter.swift
@@ -1,7 +1,7 @@
import UIKit
import Web3Wallet
-import WalletConnectPush
+import WalletConnectNotify
final class MainRouter {
weak var viewController: UIViewController!
@@ -31,9 +31,19 @@ final class MainRouter {
return SettingsModule.create(app: app)
.wrapToNavigationController()
}
+
+ func present(proposal: Session.Proposal, importAccount: ImportAccount, context: VerifyContext?) {
+ SessionProposalModule.create(app: app, importAccount: importAccount, proposal: proposal, context: context)
+ .presentFullScreen(from: viewController, transparentBackground: true)
+ }
+
+ func present(sessionRequest: Request, importAccount: ImportAccount, sessionContext: VerifyContext?) {
+ SessionRequestModule.create(app: app, sessionRequest: sessionRequest, importAccount: importAccount, sessionContext: sessionContext)
+ .presentFullScreen(from: viewController, transparentBackground: true)
+ }
- func present(pushRequest: PushRequest) {
-// PushRequestModule.create(app: app, pushRequest: pushRequest)
-// .presentFullScreen(from: viewController, transparentBackground: true)
+ func present(request: AuthRequest, importAccount: ImportAccount, context: VerifyContext?) {
+ AuthRequestModule.create(app: app, request: request, importAccount: importAccount, context: context)
+ .presentFullScreen(from: viewController, transparentBackground: true)
}
}
diff --git a/Example/WalletApp/PresentationLayer/Wallet/Main/MainViewController.swift b/Example/WalletApp/PresentationLayer/Wallet/Main/MainViewController.swift
index 5fd88ac40..f36c7be7a 100644
--- a/Example/WalletApp/PresentationLayer/Wallet/Main/MainViewController.swift
+++ b/Example/WalletApp/PresentationLayer/Wallet/Main/MainViewController.swift
@@ -1,5 +1,9 @@
import UIKit
-
+import Sentry
+enum LoginError: Error {
+ case wrongUser(id: String)
+ case wrongPassword
+}
final class MainViewController: UITabBarController {
private let presenter: MainPresenter
@@ -11,7 +15,6 @@ final class MainViewController: UITabBarController {
override func viewDidLoad() {
super.viewDidLoad()
-
setupTabs()
}
diff --git a/Example/WalletApp/PresentationLayer/Wallet/Main/Model/TabPage.swift b/Example/WalletApp/PresentationLayer/Wallet/Main/Model/TabPage.swift
index 1c93883ad..3f2051baf 100644
--- a/Example/WalletApp/PresentationLayer/Wallet/Main/Model/TabPage.swift
+++ b/Example/WalletApp/PresentationLayer/Wallet/Main/Model/TabPage.swift
@@ -2,6 +2,7 @@ import UIKit
enum TabPage: CaseIterable {
case wallet
+ case notifications
case web3Inbox
case settings
@@ -9,6 +10,8 @@ enum TabPage: CaseIterable {
switch self {
case .wallet:
return "Apps"
+ case .notifications:
+ return "Notifications"
case .web3Inbox:
return "Web3Inbox"
case .settings:
@@ -20,6 +23,8 @@ enum TabPage: CaseIterable {
switch self {
case .wallet:
return UIImage(systemName: "house.fill")!
+ case .notifications:
+ return UIImage(systemName: "bell.fill")!
case .web3Inbox:
return UIImage(systemName: "bell.fill")!
case .settings:
@@ -32,6 +37,6 @@ enum TabPage: CaseIterable {
}
static var enabledTabs: [TabPage] {
- return [.wallet, .web3Inbox, .settings]
+ return [.wallet, .notifications, .web3Inbox, .settings]
}
}
diff --git a/Example/WalletApp/PresentationLayer/Wallet/Notifications/Models/SubscriptionsViewModel.swift b/Example/WalletApp/PresentationLayer/Wallet/Notifications/Models/SubscriptionsViewModel.swift
index caf9826ee..e727c94e3 100644
--- a/Example/WalletApp/PresentationLayer/Wallet/Notifications/Models/SubscriptionsViewModel.swift
+++ b/Example/WalletApp/PresentationLayer/Wallet/Notifications/Models/SubscriptionsViewModel.swift
@@ -1,8 +1,8 @@
import Foundation
-import WalletConnectPush
+import WalletConnectNotify
struct SubscriptionsViewModel: Identifiable {
- let subscription: WalletConnectPush.PushSubscription
+ let subscription: NotifySubscription
var id: String {
return subscription.topic
diff --git a/Example/WalletApp/PresentationLayer/Wallet/Notifications/NotificationsInteractor.swift b/Example/WalletApp/PresentationLayer/Wallet/Notifications/NotificationsInteractor.swift
index f07d922f7..84797d283 100644
--- a/Example/WalletApp/PresentationLayer/Wallet/Notifications/NotificationsInteractor.swift
+++ b/Example/WalletApp/PresentationLayer/Wallet/Notifications/NotificationsInteractor.swift
@@ -1,20 +1,20 @@
-import WalletConnectPush
+import WalletConnectNotify
import Combine
final class NotificationsInteractor {
- var subscriptionsPublisher: AnyPublisher<[PushSubscription], Never> {
- return Push.wallet.subscriptionsPublisher
+ var subscriptionsPublisher: AnyPublisher<[NotifySubscription], Never> {
+ return Notify.instance.subscriptionsPublisher
}
- func getSubscriptions() -> [PushSubscription] {
- let subs = Push.wallet.getActiveSubscriptions()
+ func getSubscriptions() -> [NotifySubscription] {
+ let subs = Notify.instance.getActiveSubscriptions()
return subs
}
- func removeSubscription(_ subscription: PushSubscription) async {
+ func removeSubscription(_ subscription: NotifySubscription) async {
do {
- try await Push.wallet.deleteSubscription(topic: subscription.topic)
+ try await Notify.instance.deleteSubscription(topic: subscription.topic)
} catch {
print(error)
}
diff --git a/Example/WalletApp/PresentationLayer/Wallet/Notifications/NotificationsPresenter.swift b/Example/WalletApp/PresentationLayer/Wallet/Notifications/NotificationsPresenter.swift
index ba6761a50..0aa9d878d 100644
--- a/Example/WalletApp/PresentationLayer/Wallet/Notifications/NotificationsPresenter.swift
+++ b/Example/WalletApp/PresentationLayer/Wallet/Notifications/NotificationsPresenter.swift
@@ -54,8 +54,8 @@ private extension NotificationsPresenter {
}
interactor.subscriptionsPublisher
.receive(on: DispatchQueue.main)
- .sink { [weak self] pushSubscriptions in
- self?.subscriptions = pushSubscriptions
+ .sink { [weak self] notifySubscriptions in
+ self?.subscriptions = notifySubscriptions
.map { SubscriptionsViewModel(subscription: $0) }
}
.store(in: &disposeBag)
diff --git a/Example/WalletApp/PresentationLayer/Wallet/Notifications/NotificationsRouter.swift b/Example/WalletApp/PresentationLayer/Wallet/Notifications/NotificationsRouter.swift
index 1441d32ba..eebcfd7b3 100644
--- a/Example/WalletApp/PresentationLayer/Wallet/Notifications/NotificationsRouter.swift
+++ b/Example/WalletApp/PresentationLayer/Wallet/Notifications/NotificationsRouter.swift
@@ -1,5 +1,5 @@
import UIKit
-import WalletConnectPush
+import WalletConnectNotify
final class NotificationsRouter {
@@ -11,7 +11,7 @@ final class NotificationsRouter {
self.app = app
}
- func presentNotifications(subscription: WalletConnectPush.PushSubscription) {
+ func presentNotifications(subscription: NotifySubscription) {
PushMessagesModule.create(app: app, subscription: subscription)
.push(from: viewController)
}
diff --git a/Example/WalletApp/PresentationLayer/Wallet/PushMessages/Models/PushMessageViewModel.swift b/Example/WalletApp/PresentationLayer/Wallet/PushMessages/Models/PushMessageViewModel.swift
index 35a61e1d6..cfb200823 100644
--- a/Example/WalletApp/PresentationLayer/Wallet/PushMessages/Models/PushMessageViewModel.swift
+++ b/Example/WalletApp/PresentationLayer/Wallet/PushMessages/Models/PushMessageViewModel.swift
@@ -1,10 +1,9 @@
-
import Foundation
-import WalletConnectPush
+import WalletConnectNotify
struct PushMessageViewModel: Identifiable {
- let pushMessageRecord: WalletConnectPush.PushMessageRecord
+ let pushMessageRecord: NotifyMessageRecord
var id: String {
return pushMessageRecord.id
diff --git a/Example/WalletApp/PresentationLayer/Wallet/PushMessages/PushMessagesInteractor.swift b/Example/WalletApp/PresentationLayer/Wallet/PushMessages/PushMessagesInteractor.swift
index b2fa4a51f..b1953934e 100644
--- a/Example/WalletApp/PresentationLayer/Wallet/PushMessages/PushMessagesInteractor.swift
+++ b/Example/WalletApp/PresentationLayer/Wallet/PushMessages/PushMessagesInteractor.swift
@@ -1,23 +1,23 @@
-import WalletConnectPush
+import WalletConnectNotify
import Combine
final class PushMessagesInteractor {
- let subscription: PushSubscription
+ let subscription: NotifySubscription
- init(subscription: PushSubscription) {
+ init(subscription: NotifySubscription) {
self.subscription = subscription
}
- var pushMessagePublisher: AnyPublisher {
- return Push.wallet.pushMessagePublisher
+ var messagesPublisher: AnyPublisher<[NotifyMessageRecord], Never> {
+ return Notify.instance.messagesPublisher(topic: subscription.topic)
}
- func getPushMessages() -> [PushMessageRecord] {
- return Push.wallet.getMessageHistory(topic: subscription.topic)
+ func getPushMessages() -> [NotifyMessageRecord] {
+ return Notify.instance.getMessageHistory(topic: subscription.topic)
}
func deletePushMessage(id: String) {
- Push.wallet.deletePushMessage(id: id)
+ Notify.instance.deleteNotifyMessage(id: id)
}
}
diff --git a/Example/WalletApp/PresentationLayer/Wallet/PushMessages/PushMessagesModule.swift b/Example/WalletApp/PresentationLayer/Wallet/PushMessages/PushMessagesModule.swift
index e7b6cc382..447c08ce3 100644
--- a/Example/WalletApp/PresentationLayer/Wallet/PushMessages/PushMessagesModule.swift
+++ b/Example/WalletApp/PresentationLayer/Wallet/PushMessages/PushMessagesModule.swift
@@ -1,10 +1,10 @@
import SwiftUI
-import WalletConnectPush
+import WalletConnectNotify
final class PushMessagesModule {
@discardableResult
- static func create(app: Application, subscription: PushSubscription) -> UIViewController {
+ static func create(app: Application, subscription: NotifySubscription) -> UIViewController {
let router = PushMessagesRouter(app: app)
let interactor = PushMessagesInteractor(subscription: subscription)
let presenter = PushMessagesPresenter(interactor: interactor, router: router)
diff --git a/Example/WalletApp/PresentationLayer/Wallet/PushMessages/PushMessagesPresenter.swift b/Example/WalletApp/PresentationLayer/Wallet/PushMessages/PushMessagesPresenter.swift
index 1abf8a408..ec8b91c8f 100644
--- a/Example/WalletApp/PresentationLayer/Wallet/PushMessages/PushMessagesPresenter.swift
+++ b/Example/WalletApp/PresentationLayer/Wallet/PushMessages/PushMessagesPresenter.swift
@@ -1,6 +1,6 @@
import UIKit
import Combine
-import WalletConnectPush
+import WalletConnectNotify
final class PushMessagesPresenter: ObservableObject {
@@ -8,19 +8,35 @@ final class PushMessagesPresenter: ObservableObject {
private let router: PushMessagesRouter
private var disposeBag = Set()
- @Published var pushMessages: [PushMessageViewModel] = []
+ @Published private var pushMessages: [NotifyMessageRecord] = []
+
+ var messages: [PushMessageViewModel] {
+ return pushMessages
+ .sorted { $0.publishedAt > $1.publishedAt }
+ .map { PushMessageViewModel(pushMessageRecord: $0) }
+ }
init(interactor: PushMessagesInteractor, router: PushMessagesRouter) {
- defer { reloadPushMessages() }
+ defer { setupInitialState() }
self.interactor = interactor
self.router = router
+ setUpMessagesRefresh()
+ }
+
+ private func setUpMessagesRefresh() {
+ Timer.publish(every: 10.0, on: .main, in: .default)
+ .autoconnect()
+ .sink(receiveValue: { [weak self] _ in
+ guard let self = self else { return }
+ self.pushMessages = self.interactor.getPushMessages()
+ }).store(in: &disposeBag)
}
+
func deletePushMessage(at indexSet: IndexSet) {
if let index = indexSet.first {
interactor.deletePushMessage(id: pushMessages[index].id)
}
- reloadPushMessages()
}
}
@@ -40,27 +56,14 @@ extension PushMessagesPresenter: SceneViewModel {
private extension PushMessagesPresenter {
- func reloadPushMessages() {
- self.pushMessages = interactor.getPushMessages()
- .sorted {
- // Most recent first
- $0.publishedAt > $1.publishedAt
- }
- .map { pushMessageRecord in
- PushMessageViewModel(pushMessageRecord: pushMessageRecord)
- }
-
- interactor.pushMessagePublisher
+ func setupInitialState() {
+ pushMessages = interactor.getPushMessages()
+
+ interactor.messagesPublisher
.receive(on: DispatchQueue.main)
- .sink { [weak self] newPushMessage in
- let newMessageViewModel = PushMessageViewModel(pushMessageRecord: newPushMessage)
- guard let index = self?.pushMessages.firstIndex(
- where: { $0.pushMessageRecord.publishedAt > newPushMessage.publishedAt }
- ) else {
- self?.pushMessages.append(newMessageViewModel)
- return
- }
- self?.pushMessages.insert(newMessageViewModel, at: index)
+ .sink { [weak self] messages in
+ guard let self = self else { return }
+ self.pushMessages = self.interactor.getPushMessages()
}
.store(in: &disposeBag)
}
diff --git a/Example/WalletApp/PresentationLayer/Wallet/PushMessages/PushMessagesView.swift b/Example/WalletApp/PresentationLayer/Wallet/PushMessages/PushMessagesView.swift
index c29766ed3..7f22c53f6 100644
--- a/Example/WalletApp/PresentationLayer/Wallet/PushMessages/PushMessagesView.swift
+++ b/Example/WalletApp/PresentationLayer/Wallet/PushMessages/PushMessagesView.swift
@@ -11,7 +11,7 @@ struct PushMessagesView: View {
VStack(alignment: .leading, spacing: 16) {
ZStack {
- if presenter.pushMessages.isEmpty {
+ if presenter.messages.isEmpty {
VStack(spacing: 10) {
Image(systemName: "bell.badge.fill")
.resizable()
@@ -29,9 +29,9 @@ struct PushMessagesView: View {
}
VStack {
- if !presenter.pushMessages.isEmpty {
+ if !presenter.messages.isEmpty {
List {
- ForEach(presenter.pushMessages, id: \.id) { pm in
+ ForEach(presenter.messages, id: \.id) { pm in
notificationView(pushMessage: pm)
.listRowSeparator(.hidden)
.listRowInsets(EdgeInsets(top: 0, leading: 0, bottom: 16, trailing: 0))
diff --git a/Example/WalletApp/PresentationLayer/Wallet/PushRequest/PushRequestInteractor.swift b/Example/WalletApp/PresentationLayer/Wallet/PushRequest/PushRequestInteractor.swift
deleted file mode 100644
index 21610770c..000000000
--- a/Example/WalletApp/PresentationLayer/Wallet/PushRequest/PushRequestInteractor.swift
+++ /dev/null
@@ -1,12 +0,0 @@
-import Foundation
-import WalletConnectPush
-
-final class PushRequestInteractor {
- func approve(pushRequest: PushRequest, importAccount: ImportAccount) async throws {
- try await Push.wallet.approve(id: pushRequest.id, onSign: importAccount.onSign)
- }
-
- func reject(pushRequest: PushRequest) async throws {
- try await Push.wallet.reject(id: pushRequest.id)
- }
-}
diff --git a/Example/WalletApp/PresentationLayer/Wallet/PushRequest/PushRequestModule.swift b/Example/WalletApp/PresentationLayer/Wallet/PushRequest/PushRequestModule.swift
deleted file mode 100644
index ffb464f0f..000000000
--- a/Example/WalletApp/PresentationLayer/Wallet/PushRequest/PushRequestModule.swift
+++ /dev/null
@@ -1,17 +0,0 @@
-import SwiftUI
-import WalletConnectPush
-
-final class PushRequestModule {
- @discardableResult
- static func create(app: Application, pushRequest: PushRequest, importAccount: ImportAccount) -> UIViewController {
- let router = PushRequestRouter(app: app)
- let interactor = PushRequestInteractor()
- let presenter = PushRequestPresenter(interactor: interactor, router: router, pushRequest: pushRequest, importAccount: importAccount)
- let view = PushRequestView().environmentObject(presenter)
- let viewController = SceneViewController(viewModel: presenter, content: view)
-
- router.viewController = viewController
-
- return viewController
- }
-}
diff --git a/Example/WalletApp/PresentationLayer/Wallet/PushRequest/PushRequestPresenter.swift b/Example/WalletApp/PresentationLayer/Wallet/PushRequest/PushRequestPresenter.swift
deleted file mode 100644
index 19e2dadd3..000000000
--- a/Example/WalletApp/PresentationLayer/Wallet/PushRequest/PushRequestPresenter.swift
+++ /dev/null
@@ -1,54 +0,0 @@
-import UIKit
-import Combine
-import WalletConnectPush
-
-final class PushRequestPresenter: ObservableObject {
- private let interactor: PushRequestInteractor
- private let router: PushRequestRouter
- private let importAccount: ImportAccount
-
- let pushRequest: PushRequest
-
- var message: String {
- return String(describing: pushRequest.account)
- }
-
- private var disposeBag = Set()
-
- init(
- interactor: PushRequestInteractor,
- router: PushRequestRouter,
- pushRequest: PushRequest,
- importAccount: ImportAccount
- ) {
- defer { setupInitialState() }
- self.interactor = interactor
- self.router = router
- self.pushRequest = pushRequest
- self.importAccount = importAccount
- }
-
- @MainActor
- func onApprove() async throws {
- try await interactor.approve(pushRequest: pushRequest, importAccount: importAccount)
- router.dismiss()
- }
-
- @MainActor
- func onReject() async throws {
- try await interactor.reject(pushRequest: pushRequest)
- router.dismiss()
- }
-}
-
-// MARK: - Private functions
-private extension PushRequestPresenter {
- func setupInitialState() {
-
- }
-}
-
-// MARK: - SceneViewModel
-extension PushRequestPresenter: SceneViewModel {
-
-}
diff --git a/Example/WalletApp/PresentationLayer/Wallet/PushRequest/PushRequestRouter.swift b/Example/WalletApp/PresentationLayer/Wallet/PushRequest/PushRequestRouter.swift
deleted file mode 100644
index 6ac5f730c..000000000
--- a/Example/WalletApp/PresentationLayer/Wallet/PushRequest/PushRequestRouter.swift
+++ /dev/null
@@ -1,15 +0,0 @@
-import UIKit
-
-final class PushRequestRouter {
- weak var viewController: UIViewController!
-
- private let app: Application
-
- init(app: Application) {
- self.app = app
- }
-
- func dismiss() {
- viewController.dismiss()
- }
-}
diff --git a/Example/WalletApp/PresentationLayer/Wallet/PushRequest/PushRequestView.swift b/Example/WalletApp/PresentationLayer/Wallet/PushRequest/PushRequestView.swift
deleted file mode 100644
index 62a21e17f..000000000
--- a/Example/WalletApp/PresentationLayer/Wallet/PushRequest/PushRequestView.swift
+++ /dev/null
@@ -1,127 +0,0 @@
-import SwiftUI
-
-struct PushRequestView: View {
- @EnvironmentObject var presenter: PushRequestPresenter
-
- @State var text = ""
-
- var body: some View {
- ZStack {
- Color.black.opacity(0.6)
-
- VStack {
- Spacer()
-
- VStack(spacing: 0) {
- Image("header")
- .resizable()
- .scaledToFit()
-
- Text("would you like to send notifications")
- .foregroundColor(.grey8)
- .font(.system(size: 22, weight: .bold, design: .rounded))
- .padding(.top, 10)
-
- pushRequestView()
-
- HStack(spacing: 20) {
- Button {
- Task(priority: .userInitiated) { try await
- presenter.onReject()
- }
- } label: {
- Text("Decline")
- .frame(maxWidth: .infinity)
- .foregroundColor(.white)
- .font(.system(size: 20, weight: .semibold, design: .rounded))
- .padding(.vertical, 11)
- .background(
- LinearGradient(
- gradient: Gradient(colors: [
- .foregroundNegative,
- .lightForegroundNegative
- ]),
- startPoint: .top, endPoint: .bottom)
- )
- .cornerRadius(20)
- }
- .shadow(color: .white.opacity(0.25), radius: 8, y: 2)
-
- Button {
- Task(priority: .userInitiated) { try await
- presenter.onApprove()
- }
- } label: {
- Text("Allow")
- .frame(maxWidth: .infinity)
- .foregroundColor(.white)
- .font(.system(size: 20, weight: .semibold, design: .rounded))
- .padding(.vertical, 11)
- .background(
- LinearGradient(
- gradient: Gradient(colors: [
- .foregroundPositive,
- .lightForegroundPositive
- ]),
- startPoint: .top, endPoint: .bottom)
- )
- .cornerRadius(20)
- }
- .shadow(color: .white.opacity(0.25), radius: 8, y: 2)
- }
- .padding(.top, 25)
- }
- .padding(20)
- .background(.ultraThinMaterial)
- .cornerRadius(34)
- .padding(.horizontal, 10)
-
- Spacer()
- }
- }
- .edgesIgnoringSafeArea(.all)
- }
-
- private func pushRequestView() -> some View {
- VStack {
- VStack(alignment: .leading) {
- Text("Notifications")
- .font(.system(size: 15, weight: .semibold, design: .rounded))
- .foregroundColor(.whiteBackground)
- .padding(.horizontal, 8)
- .padding(.vertical, 5)
- .background(Color.grey70)
- .cornerRadius(28, corners: .allCorners)
- .padding(.leading, 15)
- .padding(.top, 9)
-
- VStack(spacing: 0) {
- ScrollView {
- Text(presenter.message)
- .foregroundColor(.grey50)
- .font(.system(size: 13, weight: .semibold, design: .rounded))
- }
- .padding(.horizontal, 18)
- .padding(.vertical, 10)
- .frame(height: 250)
- }
- .background(Color.whiteBackground)
- .cornerRadius(20, corners: .allCorners)
- .padding(.horizontal, 5)
- .padding(.bottom, 5)
-
- }
- .background(.thinMaterial)
- .cornerRadius(25, corners: .allCorners)
- }
- .padding(.top, 30)
- }
-}
-
-#if DEBUG
-struct PushRequestView_Previews: PreviewProvider {
- static var previews: some View {
- PushRequestView()
- }
-}
-#endif
diff --git a/Example/WalletApp/PresentationLayer/Wallet/SessionProposal/SessionProposalInteractor.swift b/Example/WalletApp/PresentationLayer/Wallet/SessionProposal/SessionProposalInteractor.swift
index 226f5b80d..0216a3db6 100644
--- a/Example/WalletApp/PresentationLayer/Wallet/SessionProposal/SessionProposalInteractor.swift
+++ b/Example/WalletApp/PresentationLayer/Wallet/SessionProposal/SessionProposalInteractor.swift
@@ -20,18 +20,14 @@ final class SessionProposalInteractor {
let supportedChains = [Blockchain("eip155:1")!, Blockchain("eip155:137")!]
let supportedAccounts = [Account(blockchain: Blockchain("eip155:1")!, address: ETHSigner.address)!, Account(blockchain: Blockchain("eip155:137")!, address: ETHSigner.address)!]
*/
- do {
- let sessionNamespaces = try AutoNamespaces.build(
- sessionProposal: proposal,
- chains: Array(supportedChains),
- methods: Array(supportedMethods),
- events: Array(supportedEvents),
- accounts: supportedAccounts
- )
- try await Web3Wallet.instance.approve(proposalId: proposal.id, namespaces: sessionNamespaces, sessionProperties: proposal.sessionProperties)
- } catch {
- print(error)
- }
+ let sessionNamespaces = try AutoNamespaces.build(
+ sessionProposal: proposal,
+ chains: Array(supportedChains),
+ methods: Array(supportedMethods),
+ events: Array(supportedEvents),
+ accounts: supportedAccounts
+ )
+ try await Web3Wallet.instance.approve(proposalId: proposal.id, namespaces: sessionNamespaces, sessionProperties: proposal.sessionProperties)
}
func reject(proposal: Session.Proposal) async throws {
diff --git a/Example/WalletApp/PresentationLayer/Wallet/SessionProposal/SessionProposalPresenter.swift b/Example/WalletApp/PresentationLayer/Wallet/SessionProposal/SessionProposalPresenter.swift
index 117db225c..ad02dd7a4 100644
--- a/Example/WalletApp/PresentationLayer/Wallet/SessionProposal/SessionProposalPresenter.swift
+++ b/Example/WalletApp/PresentationLayer/Wallet/SessionProposal/SessionProposalPresenter.swift
@@ -11,6 +11,9 @@ final class SessionProposalPresenter: ObservableObject {
let sessionProposal: Session.Proposal
let verified: Bool?
+ @Published var showError = false
+ @Published var errorMessage = "Error"
+
private var disposeBag = Set()
init(
@@ -30,14 +33,24 @@ final class SessionProposalPresenter: ObservableObject {
@MainActor
func onApprove() async throws {
- try await interactor.approve(proposal: sessionProposal, account: importAccount.account)
- router.dismiss()
+ do {
+ try await interactor.approve(proposal: sessionProposal, account: importAccount.account)
+ router.dismiss()
+ } catch {
+ errorMessage = error.localizedDescription
+ showError.toggle()
+ }
}
@MainActor
func onReject() async throws {
- try await interactor.reject(proposal: sessionProposal)
- router.dismiss()
+ do {
+ try await interactor.reject(proposal: sessionProposal)
+ router.dismiss()
+ } catch {
+ errorMessage = error.localizedDescription
+ showError.toggle()
+ }
}
}
diff --git a/Example/WalletApp/PresentationLayer/Wallet/SessionProposal/SessionProposalView.swift b/Example/WalletApp/PresentationLayer/Wallet/SessionProposal/SessionProposalView.swift
index 97717d9b5..8082ce1ee 100644
--- a/Example/WalletApp/PresentationLayer/Wallet/SessionProposal/SessionProposalView.swift
+++ b/Example/WalletApp/PresentationLayer/Wallet/SessionProposal/SessionProposalView.swift
@@ -146,6 +146,9 @@ struct SessionProposalView: View {
Spacer()
}
}
+ .alert(presenter.errorMessage, isPresented: $presenter.showError) {
+ Button("OK", role: .cancel) {}
+ }
.edgesIgnoringSafeArea(.all)
}
//private func sessionProposalView(chain: String) -> some View {
diff --git a/Example/WalletApp/PresentationLayer/Wallet/Wallet/WalletInteractor.swift b/Example/WalletApp/PresentationLayer/Wallet/Wallet/WalletInteractor.swift
index 564c31f64..3a32be3f8 100644
--- a/Example/WalletApp/PresentationLayer/Wallet/Wallet/WalletInteractor.swift
+++ b/Example/WalletApp/PresentationLayer/Wallet/Wallet/WalletInteractor.swift
@@ -1,21 +1,9 @@
import Combine
import Web3Wallet
-import WalletConnectPush
+import WalletConnectNotify
final class WalletInteractor {
- var sessionProposalPublisher: AnyPublisher<(proposal: Session.Proposal, context: VerifyContext?), Never> {
- return Web3Wallet.instance.sessionProposalPublisher
- }
-
- var sessionRequestPublisher: AnyPublisher<(request: Request, context: VerifyContext?), Never> {
- return Web3Wallet.instance.sessionRequestPublisher
- }
-
- var requestPublisher: AnyPublisher<(request: AuthRequest, context: VerifyContext?), Never> {
- return Web3Wallet.instance.authRequestPublisher
- }
-
var sessionsPublisher: AnyPublisher<[Session], Never> {
return Web3Wallet.instance.sessionsPublisher
}
@@ -31,4 +19,12 @@ final class WalletInteractor {
func disconnectSession(session: Session) async throws {
try await Web3Wallet.instance.disconnect(topic: session.topic)
}
+
+ func getPendingProposals() -> [(proposal: Session.Proposal, context: VerifyContext?)] {
+ Web3Wallet.instance.getPendingProposals()
+ }
+
+ func getPendingRequests() -> [(request: Request, context: VerifyContext?)] {
+ Web3Wallet.instance.getPendingRequests()
+ }
}
diff --git a/Example/WalletApp/PresentationLayer/Wallet/Wallet/WalletPresenter.swift b/Example/WalletApp/PresentationLayer/Wallet/Wallet/WalletPresenter.swift
index cb8bc32fe..c8a5baee1 100644
--- a/Example/WalletApp/PresentationLayer/Wallet/Wallet/WalletPresenter.swift
+++ b/Example/WalletApp/PresentationLayer/Wallet/Wallet/WalletPresenter.swift
@@ -40,6 +40,11 @@ final class WalletPresenter: ObservableObject {
func onAppear() {
showPairingLoading = app.requestSent
removePairingIndicator()
+
+ let pendingRequests = interactor.getPendingRequests()
+ if let request = pendingRequests.first(where: { $0.context != nil }) {
+ router.present(sessionRequest: request.request, importAccount: importAccount, sessionContext: request.context)
+ }
}
func onConnection(session: Session) {
@@ -88,29 +93,6 @@ final class WalletPresenter: ObservableObject {
// MARK: - Private functions
extension WalletPresenter {
private func setupInitialState() {
- interactor.sessionProposalPublisher
- .receive(on: DispatchQueue.main)
- .sink { [unowned self] session in
- showPairingLoading = false
- router.present(proposal: session.proposal, importAccount: importAccount, context: session.context)
- }
- .store(in: &disposeBag)
-
- interactor.sessionRequestPublisher
- .receive(on: DispatchQueue.main)
- .sink { [unowned self] request, context in
- showPairingLoading = false
- router.present(sessionRequest: request, importAccount: importAccount, sessionContext: context)
- }.store(in: &disposeBag)
-
- interactor.requestPublisher
- .receive(on: DispatchQueue.main)
- .sink { [unowned self] result in
- showPairingLoading = false
- router.present(request: result.request, importAccount: importAccount, context: result.context)
- }
- .store(in: &disposeBag)
-
interactor.sessionsPublisher
.receive(on: DispatchQueue.main)
.sink { [weak self] sessions in
@@ -148,10 +130,6 @@ extension WalletPresenter {
private func removePairingIndicator() {
DispatchQueue.main.asyncAfter(deadline: .now() + 3) {
- if self.showPairingLoading {
- self.errorMessage = "WalletConnect - Pairing timeout error"
- self.showError.toggle()
- }
self.showPairingLoading = false
}
}
diff --git a/Example/WalletApp/PresentationLayer/Wallet/Wallet/WalletRouter.swift b/Example/WalletApp/PresentationLayer/Wallet/Wallet/WalletRouter.swift
index 12604e895..c9907a0b5 100644
--- a/Example/WalletApp/PresentationLayer/Wallet/Wallet/WalletRouter.swift
+++ b/Example/WalletApp/PresentationLayer/Wallet/Wallet/WalletRouter.swift
@@ -11,18 +11,13 @@ final class WalletRouter {
self.app = app
}
- func present(proposal: Session.Proposal, importAccount: ImportAccount, context: VerifyContext?) {
- SessionProposalModule.create(app: app, importAccount: importAccount, proposal: proposal, context: context)
- .presentFullScreen(from: viewController, transparentBackground: true)
- }
-
func present(sessionRequest: Request, importAccount: ImportAccount, sessionContext: VerifyContext?) {
SessionRequestModule.create(app: app, sessionRequest: sessionRequest, importAccount: importAccount, sessionContext: sessionContext)
.presentFullScreen(from: viewController, transparentBackground: true)
}
-
- func present(request: AuthRequest, importAccount: ImportAccount, context: VerifyContext?) {
- AuthRequestModule.create(app: app, request: request, importAccount: importAccount, context: context)
+
+ func present(sessionProposal: Session.Proposal, importAccount: ImportAccount, sessionContext: VerifyContext?) {
+ SessionProposalModule.create(app: app, importAccount: importAccount, proposal: sessionProposal, context: sessionContext)
.presentFullScreen(from: viewController, transparentBackground: true)
}
diff --git a/Example/WalletApp/PresentationLayer/Wallet/Web3Inbox/Web3InboxViewController.swift b/Example/WalletApp/PresentationLayer/Wallet/Web3Inbox/Web3InboxViewController.swift
index 223c16a85..d79c8b4ea 100644
--- a/Example/WalletApp/PresentationLayer/Wallet/Web3Inbox/Web3InboxViewController.swift
+++ b/Example/WalletApp/PresentationLayer/Wallet/Web3Inbox/Web3InboxViewController.swift
@@ -25,11 +25,22 @@ final class Web3InboxViewController: UIViewController {
view = Web3Inbox.instance.getWebView()
let refresh = UIBarButtonItem(barButtonSystemItem: .refresh, target: self, action: #selector(refreshTapped))
- navigationItem.rightBarButtonItem = refresh
+ let getUrl = UIBarButtonItem(barButtonSystemItem: .compose, target: self, action: #selector(getUrlPressed))
+
+ navigationItem.rightBarButtonItems = [refresh, getUrl]
}
@objc func refreshTapped() {
- webView?.reload()
+ Web3Inbox.instance.reload()
+ }
+
+ @objc func getUrlPressed(_ sender: UIBarItem) {
+ UIPasteboard.general.string = webView?.url?.absoluteString
+
+ let alert = UIAlertController(title: "URL copied to clipboard", message: nil, preferredStyle: .alert)
+ let action = UIAlertAction(title: "OK", style: .cancel)
+ alert.addAction(action)
+ present(alert, animated: true)
}
}
diff --git a/Example/WalletConnect-Package.xctestplan b/Example/WalletConnect-Package.xctestplan
new file mode 100644
index 000000000..d2c5cb9e5
--- /dev/null
+++ b/Example/WalletConnect-Package.xctestplan
@@ -0,0 +1,108 @@
+{
+ "configurations" : [
+ {
+ "id" : "BDCC881D-E5D4-477B-A899-4984FA326FED",
+ "name" : "Test Scheme Action",
+ "options" : {
+
+ }
+ }
+ ],
+ "defaultOptions" : {
+
+ },
+ "testTargets" : [
+ {
+ "target" : {
+ "containerPath" : "container:",
+ "identifier" : "AuthTests",
+ "name" : "AuthTests"
+ }
+ },
+ {
+ "target" : {
+ "containerPath" : "container:",
+ "identifier" : "ChatTests",
+ "name" : "ChatTests"
+ }
+ },
+ {
+ "target" : {
+ "containerPath" : "container:",
+ "identifier" : "CommonsTests",
+ "name" : "CommonsTests"
+ }
+ },
+ {
+ "target" : {
+ "containerPath" : "container:",
+ "identifier" : "JSONRPCTests",
+ "name" : "JSONRPCTests"
+ }
+ },
+ {
+ "target" : {
+ "containerPath" : "container:",
+ "identifier" : "RelayerTests",
+ "name" : "RelayerTests"
+ }
+ },
+ {
+ "target" : {
+ "containerPath" : "container:",
+ "identifier" : "VerifyTests",
+ "name" : "VerifyTests"
+ }
+ },
+ {
+ "target" : {
+ "containerPath" : "container:",
+ "identifier" : "WalletConnectKMSTests",
+ "name" : "WalletConnectKMSTests"
+ }
+ },
+ {
+ "target" : {
+ "containerPath" : "container:",
+ "identifier" : "WalletConnectPairingTests",
+ "name" : "WalletConnectPairingTests"
+ }
+ },
+ {
+ "target" : {
+ "containerPath" : "container:",
+ "identifier" : "WalletConnectSignTests",
+ "name" : "WalletConnectSignTests"
+ }
+ },
+ {
+ "target" : {
+ "containerPath" : "container:",
+ "identifier" : "WalletConnectUtilsTests",
+ "name" : "WalletConnectUtilsTests"
+ }
+ },
+ {
+ "target" : {
+ "containerPath" : "container:",
+ "identifier" : "Web3WalletTests",
+ "name" : "Web3WalletTests"
+ }
+ },
+ {
+ "target" : {
+ "containerPath" : "container:",
+ "identifier" : "WalletConnectModalTests",
+ "name" : "WalletConnectModalTests"
+ }
+ },
+ {
+ "target" : {
+ "containerPath" : "container:",
+ "identifier" : "NotifyTests",
+ "name" : "NotifyTests"
+ }
+ }
+ ],
+ "version" : 1
+}
diff --git a/Makefile b/Makefile
index adf185529..32cdfdd66 100755
--- a/Makefile
+++ b/Makefile
@@ -16,21 +16,31 @@ ifeq "${EXISTS_FASTLANE}" ""
endif
@echo "All dependencies was installed"
-test_setup:
- defaults write com.apple.dt.XCBuild IgnoreFileSystemDeviceInodeChanges -bool YES
- rm -rf test_results
- mkdir test_results
-
build_all:
- rm -rf test_results
- set -o pipefail && xcodebuild -scheme "WalletConnect-Package" -destination "platform=iOS Simulator,name=iPhone 14" -derivedDataPath DerivedDataCache -clonedSourcePackagesDirPath ../SourcePackagesCache RELAY_HOST='$(RELAY_HOST)' PROJECT_ID='$(PROJECT_ID)' build-for-testing | xcpretty
- set -o pipefail && xcodebuild -project "Example/ExampleApp.xcodeproj" -scheme "BuildAll" -destination "platform=iOS Simulator,name=iPhone 14" -derivedDataPath DerivedDataCache -clonedSourcePackagesDirPath ../SourcePackagesCache RELAY_HOST='$(RELAY_HOST)' PROJECT_ID='$(PROJECT_ID)' CAST_HOST='$(CAST_HOST)' build-for-testing | xcpretty
-
-build_dapp:
- fastlane build scheme:DApp
-
-build_wallet:
- fastlane build scheme:WalletApp
+ set -o pipefail && env NSUnbufferedIO=YES \
+ xcodebuild \
+ -scheme "WalletConnect-Package" \
+ -destination "platform=iOS Simulator,name=iPhone 14" \
+ -derivedDataPath DerivedDataCache \
+ -clonedSourcePackagesDirPath ../SourcePackagesCache \
+ RELAY_HOST='$(RELAY_HOST)' \
+ PROJECT_ID='$(PROJECT_ID)' \
+ build-for-testing \
+ | xcbeautify
+
+ set -o pipefail && env NSUnbufferedIO=YES \
+ xcodebuild \
+ -project "Example/ExampleApp.xcodeproj" \
+ -scheme "BuildAll" \
+ -destination "platform=iOS Simulator,name=iPhone 14" \
+ -derivedDataPath DerivedDataCache \
+ -clonedSourcePackagesDirPath ../SourcePackagesCache \
+ RELAY_HOST='$(RELAY_HOST)' \
+ PROJECT_ID='$(PROJECT_ID)' \
+ CAST_HOST='$(CAST_HOST)' \
+ JS_CLIENT_API_HOST='$(JS_CLIENT_API_HOST)' \
+ build-for-testing \
+ | xcbeautify
echo_ui_tests:
echo "EchoUITests disabled"
@@ -38,79 +48,30 @@ echo_ui_tests:
ui_tests:
echo "UI Tests disabled"
-unitxctestrun = $(shell find . -name '*WalletConnect-Package*.xctestrun')
-
-unit_tests: test_setup
-ifneq ($(unitxctestrun),)
- set -o pipefail && env NSUnbufferedIO=YES xcodebuild -destination 'platform=iOS Simulator,name=iPhone 14' -derivedDataPath DerivedDataCache -clonedSourcePackagesDirPath ../SourcePackagesCache -resultBundlePath 'test_results/UnitTests.xcresult' -xctestrun '$(unitxctestrun)' test-without-building | tee ./test_results/xcodebuild.log | xcpretty --report junit --output ./test_results/report.junit
-else
- set -o pipefail && env NSUnbufferedIO=YES xcodebuild -scheme WalletConnect-Package -destination 'platform=iOS Simulator,name=iPhone 14' -derivedDataPath DerivedDataCache -clonedSourcePackagesDirPath ../SourcePackagesCache -resultBundlePath 'test_results/UnitTests.xcresult' test | tee ./test_results/xcodebuild.log | xcpretty --report junit --output ./test_results/report.junit
-endif
-
-integrationxctestrun = $(shell find . -name '*_IntegrationTests*.xctestrun')
-
-integration_tests: test_setup
-ifneq ($(integrationxctestrun),)
-# override ENV variables
- plutil -replace TestConfigurations.0.TestTargets.0.EnvironmentVariables.RELAY_HOST -string $(RELAY_HOST) $(integrationxctestrun)
- plutil -replace TestConfigurations.0.TestTargets.0.EnvironmentVariables.PROJECT_ID -string $(PROJECT_ID) $(integrationxctestrun)
- plutil -replace TestConfigurations.0.TestTargets.0.EnvironmentVariables.GM_DAPP_PROJECT_ID -string $(GM_DAPP_PROJECT_ID) $(integrationxctestrun)
- plutil -replace TestConfigurations.0.TestTargets.0.EnvironmentVariables.GM_DAPP_PROJECT_SECRET -string $(GM_DAPP_PROJECT_SECRET) $(integrationxctestrun)
- plutil -replace TestConfigurations.0.TestTargets.0.EnvironmentVariables.CAST_HOST -string $(CAST_HOST) $(integrationxctestrun)
-# test-without-building
- set -o pipefail && env NSUnbufferedIO=YES xcodebuild -destination 'platform=iOS Simulator,name=iPhone 14' -derivedDataPath DerivedDataCache -clonedSourcePackagesDirPath ../SourcePackagesCache -resultBundlePath 'test_results/IntegrationTests.xcresult' -xctestrun '$(integrationxctestrun)' test-without-building | tee ./test_results/xcodebuild.log | xcpretty --report junit --output ./test_results/report.junit
-else
- set -o pipefail && env NSUnbufferedIO=YES xcodebuild -project Example/ExampleApp.xcodeproj -scheme IntegrationTests -testPlan IntegrationTests -destination 'platform=iOS Simulator,name=iPhone 14' -derivedDataPath DerivedDataCache -clonedSourcePackagesDirPath ../SourcePackagesCache -resultBundlePath 'test_results/IntegrationTests.xcresult' RELAY_HOST='$(RELAY_HOST)' PROJECT_ID='$(PROJECT_ID)' GM_DAPP_PROJECT_ID='$(GM_DAPP_PROJECT_ID)' GM_DAPP_PROJECT_SECRET='$(GM_DAPP_PROJECT_SECRET)' CAST_HOST='$(CAST_HOST)' test | tee ./test_results/xcodebuild.log | xcpretty --report junit --output ./test_results/report.junit
-endif
-
-relayxctestrun = $(shell find . -name '*_RelayIntegrationTests*.xctestrun')
-
-relay_tests: test_setup
-ifneq ($(relayxctestrun),)
-# override ENV variables
- plutil -replace TestConfigurations.0.TestTargets.0.EnvironmentVariables.RELAY_HOST -string $(RELAY_HOST) $(relayxctestrun)
- plutil -replace TestConfigurations.0.TestTargets.0.EnvironmentVariables.PROJECT_ID -string $(PROJECT_ID) $(relayxctestrun)
-# test-without-building
- set -o pipefail && env NSUnbufferedIO=YES xcodebuild -destination 'platform=iOS Simulator,name=iPhone 14' -derivedDataPath DerivedDataCache -resultBundlePath 'test_results/RelayIntegrationTests.xcresult' -xctestrun '$(relayxctestrun)' test-without-building | tee ./test_results/xcodebuild.log | xcpretty --report junit --output ./test_results/report.junit
-else
- set -o pipefail && env NSUnbufferedIO=YES xcodebuild -project Example/ExampleApp.xcodeproj -scheme RelayIntegrationTests -destination 'platform=iOS Simulator,name=iPhone 14' -derivedDataPath DerivedDataCache -resultBundlePath 'test_results/RelayIntegrationTests.xcresult' RELAY_HOST='$(RELAY_HOST)' PROJECT_ID='$(PROJECT_ID)' test | tee ./test_results/xcodebuild.log | xcpretty --report junit --output ./test_results/report.junit
-endif
-
-notifyxctestrun = $(shell find . -name '*_NotifyTests*.xctestrun')
-
-notify_tests: test_setup
-ifneq ($(notifyxctestrun),)
-# override ENV variables
- plutil -replace TestConfigurations.0.TestTargets.0.EnvironmentVariables.RELAY_HOST -string $(RELAY_HOST) $(notifyxctestrun)
- plutil -replace TestConfigurations.0.TestTargets.0.EnvironmentVariables.PROJECT_ID -string $(PROJECT_ID) $(notifyxctestrun)
- plutil -replace TestConfigurations.0.TestTargets.0.EnvironmentVariables.GM_DAPP_PROJECT_ID -string $(GM_DAPP_PROJECT_ID) $(notifyxctestrun)
- plutil -replace TestConfigurations.0.TestTargets.0.EnvironmentVariables.GM_DAPP_PROJECT_SECRET -string $(GM_DAPP_PROJECT_SECRET) $(notifyxctestrun)
- plutil -replace TestConfigurations.0.TestTargets.0.EnvironmentVariables.CAST_HOST -string $(CAST_HOST) $(notifyxctestrun)
-# test-without-building
- set -o pipefail && env NSUnbufferedIO=YES xcodebuild -destination 'platform=iOS Simulator,name=iPhone 14' -derivedDataPath DerivedDataCache -clonedSourcePackagesDirPath ../SourcePackagesCache -resultBundlePath 'test_results/NotifyTests.xcresult' -xctestrun '$(notifyxctestrun)' test-without-building | tee ./test_results/xcodebuild.log | xcpretty --report junit --output ./test_results/report.junit
-else
- set -o pipefail && env NSUnbufferedIO=YES xcodebuild -project Example/ExampleApp.xcodeproj -scheme NotifyTests -destination 'platform=iOS Simulator,name=iPhone 14' -derivedDataPath DerivedDataCache -resultBundlePath 'test_results/NotifyTests.xcresult' RELAY_HOST='$(RELAY_HOST)' PROJECT_ID='$(PROJECT_ID)' GM_DAPP_PROJECT_ID='$(GM_DAPP_PROJECT_ID)' GM_DAPP_PROJECT_SECRET='$(GM_DAPP_PROJECT_SECRET)' CAST_HOST='$(CAST_HOST)' test | tee ./test_results/xcodebuild.log | xcpretty --report junit --output ./test_results/report.junit
-endif
-
-smokexctestrun = $(shell find . -name '*_SmokeTests*.xctestrun')
-
-smoke_tests: test_setup
-ifneq ($(smokexctestrun),)
-# override ENV variables
- plutil -replace TestConfigurations.0.TestTargets.0.EnvironmentVariables.RELAY_HOST -string $(RELAY_HOST) $(smokexctestrun)
- plutil -replace TestConfigurations.0.TestTargets.0.EnvironmentVariables.PROJECT_ID -string $(PROJECT_ID) $(smokexctestrun)
-# test-without-building
- set -o pipefail && env NSUnbufferedIO=YES xcodebuild -destination 'platform=iOS Simulator,name=iPhone 14' -derivedDataPath DerivedDataCache -clonedSourcePackagesDirPath ../SourcePackagesCache -resultBundlePath 'test_results/SmokeTests.xcresult' -xctestrun '$(smokexctestrun)' test-without-building | tee ./test_results/xcodebuild.log | xcpretty --report junit --output ./test_results/report.junit
-else
- set -o pipefail && env NSUnbufferedIO=YES xcodebuild -project Example/ExampleApp.xcodeproj -scheme IntegrationTests -testPlan SmokeTests -destination 'platform=iOS Simulator,name=iPhone 14' -derivedDataPath DerivedDataCache -clonedSourcePackagesDirPath ../SourcePackagesCache -resultBundlePath 'test_results/SmokeTests.xcresult' RELAY_HOST='$(RELAY_HOST)' PROJECT_ID='$(PROJECT_ID)' test | tee ./test_results/xcodebuild.log | xcpretty --report junit --output ./test_results/report.junit
-endif
+unit_tests:
+ ./run_tests.sh --scheme WalletConnect-Package
+
+integration_tests:
+ ./run_tests.sh --scheme IntegrationTests --testplan IntegrationTests --project Example/ExampleApp.xcodeproj
+
+relay_tests:
+ ./run_tests.sh --scheme RelayIntegrationTests --project Example/ExampleApp.xcodeproj
+
+notify_tests:
+ ./run_tests.sh --scheme NotifyTests --project Example/ExampleApp.xcodeproj
+
+smoke_tests:
+ ./run_tests.sh --scheme IntegrationTests --testplan SmokeTests --project Example/ExampleApp.xcodeproj
+
+x_platform_protocol_tests:
+ ./run_tests.sh --scheme IntegrationTests --testplan XPlatformProtocolTests --project Example/ExampleApp.xcodeproj
release_wallet:
- fastlane release_testflight username:$(APPLE_ID) token:$(TOKEN) relay_host:$(RELAY_HOST) project_id:$(PROJECT_ID) --env WalletApp
+ fastlane release_testflight username:$(APPLE_ID) token:$(TOKEN) relay_host:$(RELAY_HOST) project_id:$(PROJECT_ID) sentry_dsn:$(WALLETAPP_SENTRY_DSN) mixpanel_token:$(MIXPANEL_TOKEN) --env WalletApp
release_showcase:
fastlane release_testflight username:$(APPLE_ID) token:$(TOKEN) relay_host:$(RELAY_HOST) project_id:$(PROJECT_ID) --env Showcase
-release_all:
- fastlane release_testflight username:$(APPLE_ID) token:$(TOKEN) relay_host:$(RELAY_HOST) project_id:$(PROJECT_ID) --env WalletApp
+release_all:
+ fastlane release_testflight username:$(APPLE_ID) token:$(TOKEN) relay_host:$(RELAY_HOST) project_id:$(PROJECT_ID) sentry_dsn:$(WALLETAPP_SENTRY_DSN) mixpanel_token:$(MIXPANEL_TOKEN) --env WalletApp
fastlane release_testflight username:$(APPLE_ID) token:$(TOKEN) relay_host:$(RELAY_HOST) project_id:$(PROJECT_ID) --env Showcase
diff --git a/NotifyTests.xctestplan b/NotifyTests.xctestplan
index 68ef8125e..fc50c4a84 100644
--- a/NotifyTests.xctestplan
+++ b/NotifyTests.xctestplan
@@ -9,6 +9,7 @@
}
],
"defaultOptions" : {
+ "codeCoverage" : false,
"environmentVariableEntries" : [
{
"key" : "RELAY_HOST",
diff --git a/Package.swift b/Package.swift
index cbbb29165..8815d2a74 100644
--- a/Package.swift
+++ b/Package.swift
@@ -25,12 +25,12 @@ let package = Package(
.library(
name: "WalletConnectPairing",
targets: ["WalletConnectPairing"]),
+ .library(
+ name: "WalletConnectNotify",
+ targets: ["WalletConnectNotify"]),
.library(
name: "WalletConnectPush",
targets: ["WalletConnectPush"]),
- .library(
- name: "WalletConnectEcho",
- targets: ["WalletConnectEcho"]),
.library(
name: "WalletConnectRouter",
targets: ["WalletConnectRouter"]),
@@ -73,16 +73,16 @@ let package = Package(
path: "Sources/Auth"),
.target(
name: "Web3Wallet",
- dependencies: ["Auth", "WalletConnectSign", "WalletConnectEcho", "WalletConnectVerify"],
+ dependencies: ["Auth", "WalletConnectSign", "WalletConnectPush", "WalletConnectVerify"],
path: "Sources/Web3Wallet"),
.target(
- name: "WalletConnectPush",
- dependencies: ["WalletConnectPairing", "WalletConnectEcho", "WalletConnectIdentity", "WalletConnectSync", "WalletConnectHistory"],
- path: "Sources/WalletConnectPush"),
+ name: "WalletConnectNotify",
+ dependencies: ["WalletConnectPairing", "WalletConnectPush", "WalletConnectIdentity", "WalletConnectSigner"],
+ path: "Sources/WalletConnectNotify"),
.target(
- name: "WalletConnectEcho",
+ name: "WalletConnectPush",
dependencies: ["WalletConnectNetworking", "WalletConnectJWT"],
- path: "Sources/WalletConnectEcho"),
+ path: "Sources/WalletConnectPush"),
.target(
name: "WalletConnectRelay",
dependencies: ["WalletConnectJWT"],
@@ -100,7 +100,7 @@ let package = Package(
dependencies: ["HTTPClient", "WalletConnectRelay"]),
.target(
name: "Web3Inbox",
- dependencies: ["WalletConnectChat", "WalletConnectPush"]),
+ dependencies: ["WalletConnectChat", "WalletConnectNotify"]),
.target(
name: "WalletConnectSigner",
dependencies: ["WalletConnectNetworking"]),
@@ -157,7 +157,7 @@ let package = Package(
dependencies: ["WalletConnectChat", "WalletConnectUtils", "TestingUtils"]),
.testTarget(
name: "NotifyTests",
- dependencies: ["WalletConnectPush", "TestingUtils"]),
+ dependencies: ["WalletConnectNotify", "TestingUtils"]),
.testTarget(
name: "AuthTests",
dependencies: ["Auth", "WalletConnectUtils", "TestingUtils", "WalletConnectVerify"]),
diff --git a/Sources/Auth/AuthClient.swift b/Sources/Auth/AuthClient.swift
index 26f3797be..8189bdea0 100644
--- a/Sources/Auth/AuthClient.swift
+++ b/Sources/Auth/AuthClient.swift
@@ -89,7 +89,7 @@ public class AuthClient: AuthClientProtocol {
/// Query pending authentication requests
/// - Returns: Pending authentication requests
- public func getPendingRequests() throws -> [AuthRequest] {
+ public func getPendingRequests() throws -> [(AuthRequest, VerifyContext?)] {
return try pendingRequestsProvider.getPendingRequests()
}
diff --git a/Sources/Auth/AuthClientFactory.swift b/Sources/Auth/AuthClientFactory.swift
index 5bdc07a41..dbddf2a53 100644
--- a/Sources/Auth/AuthClientFactory.swift
+++ b/Sources/Auth/AuthClientFactory.swift
@@ -1,7 +1,6 @@
import Foundation
public struct AuthClientFactory {
-
public static func create(
metadata: AppMetadata,
projectId: String,
@@ -9,7 +8,6 @@ public struct AuthClientFactory {
networkingClient: NetworkingInteractor,
pairingRegisterer: PairingRegisterer
) -> AuthClient {
-
let logger = ConsoleLogger(loggingLevel: .off)
let keyValueStorage = UserDefaults.standard
let keychainStorage = KeychainStorage(serviceIdentifier: "com.walletconnect.sdk")
@@ -39,19 +37,19 @@ public struct AuthClientFactory {
pairingRegisterer: PairingRegisterer,
iatProvider: IATProvider
) -> AuthClient {
-
let kms = KeyManagementService(keychain: keychainStorage)
let history = RPCHistoryFactory.createForNetwork(keyValueStorage: keyValueStorage)
let messageFormatter = SIWECacaoFormatter()
let appRequestService = AppRequestService(networkingInteractor: networkingClient, kms: kms, appMetadata: metadata, logger: logger, iatProvader: iatProvider)
let verifyClient = VerifyClientFactory.create()
+ let verifyContextStore = CodableStore(defaults: keyValueStorage, identifier: VerifyStorageIdentifiers.context.rawValue)
let messageVerifierFactory = MessageVerifierFactory(crypto: crypto)
let signatureVerifier = messageVerifierFactory.create(projectId: projectId)
let appRespondSubscriber = AppRespondSubscriber(networkingInteractor: networkingClient, logger: logger, rpcHistory: history, signatureVerifier: signatureVerifier, pairingRegisterer: pairingRegisterer, messageFormatter: messageFormatter)
let walletErrorResponder = WalletErrorResponder(networkingInteractor: networkingClient, logger: logger, kms: kms, rpcHistory: history)
- let walletRequestSubscriber = WalletRequestSubscriber(networkingInteractor: networkingClient, logger: logger, kms: kms, walletErrorResponder: walletErrorResponder, pairingRegisterer: pairingRegisterer, verifyClient: verifyClient)
- let walletRespondService = WalletRespondService(networkingInteractor: networkingClient, logger: logger, kms: kms, rpcHistory: history, walletErrorResponder: walletErrorResponder)
- let pendingRequestsProvider = PendingRequestsProvider(rpcHistory: history)
+ let walletRequestSubscriber = WalletRequestSubscriber(networkingInteractor: networkingClient, logger: logger, kms: kms, walletErrorResponder: walletErrorResponder, pairingRegisterer: pairingRegisterer, verifyClient: verifyClient, verifyContextStore: verifyContextStore)
+ let walletRespondService = WalletRespondService(networkingInteractor: networkingClient, logger: logger, kms: kms, rpcHistory: history, verifyContextStore: verifyContextStore, walletErrorResponder: walletErrorResponder, pairingRegisterer: pairingRegisterer)
+ let pendingRequestsProvider = PendingRequestsProvider(rpcHistory: history, verifyContextStore: verifyContextStore)
return AuthClient(
appRequestService: appRequestService,
diff --git a/Sources/Auth/AuthClientProtocol.swift b/Sources/Auth/AuthClientProtocol.swift
index d79048ca8..23d5cd55c 100644
--- a/Sources/Auth/AuthClientProtocol.swift
+++ b/Sources/Auth/AuthClientProtocol.swift
@@ -7,5 +7,5 @@ public protocol AuthClientProtocol {
func formatMessage(payload: AuthPayload, address: String) throws -> String
func respond(requestId: RPCID, signature: CacaoSignature, from account: Account) async throws
func reject(requestId: RPCID) async throws
- func getPendingRequests() throws -> [AuthRequest]
+ func getPendingRequests() throws -> [(AuthRequest, VerifyContext?)]
}
diff --git a/Sources/Auth/Services/Wallet/PendingRequestsProvider.swift b/Sources/Auth/Services/Wallet/PendingRequestsProvider.swift
index 5050ef778..b351be66a 100644
--- a/Sources/Auth/Services/Wallet/PendingRequestsProvider.swift
+++ b/Sources/Auth/Services/Wallet/PendingRequestsProvider.swift
@@ -2,18 +2,24 @@ import Foundation
class PendingRequestsProvider {
private let rpcHistory: RPCHistory
+ private let verifyContextStore: CodableStore
- init(rpcHistory: RPCHistory) {
+ init(
+ rpcHistory: RPCHistory,
+ verifyContextStore: CodableStore
+ ) {
self.rpcHistory = rpcHistory
+ self.verifyContextStore = verifyContextStore
}
- public func getPendingRequests() throws -> [AuthRequest] {
+ public func getPendingRequests() throws -> [(AuthRequest, VerifyContext?)] {
let pendingRequests: [AuthRequest] = rpcHistory.getPending()
.filter {$0.request.method == "wc_authRequest"}
.compactMap {
guard let params = try? $0.request.params?.get(AuthRequestParams.self) else { return nil }
return AuthRequest(id: $0.request.id!, topic: $0.topic, payload: params.payloadParams)
}
- return pendingRequests
+
+ return pendingRequests.map { ($0, try? verifyContextStore.get(key: $0.id.string)) }
}
}
diff --git a/Sources/Auth/Services/Wallet/WalletRequestSubscriber.swift b/Sources/Auth/Services/Wallet/WalletRequestSubscriber.swift
index d1b523f4b..c2a7030c0 100644
--- a/Sources/Auth/Services/Wallet/WalletRequestSubscriber.swift
+++ b/Sources/Auth/Services/Wallet/WalletRequestSubscriber.swift
@@ -9,6 +9,8 @@ class WalletRequestSubscriber {
private let walletErrorResponder: WalletErrorResponder
private let pairingRegisterer: PairingRegisterer
private let verifyClient: VerifyClientProtocol
+ private let verifyContextStore: CodableStore
+
var onRequest: (((request: AuthRequest, context: VerifyContext?)) -> Void)?
init(
@@ -17,7 +19,8 @@ class WalletRequestSubscriber {
kms: KeyManagementServiceProtocol,
walletErrorResponder: WalletErrorResponder,
pairingRegisterer: PairingRegisterer,
- verifyClient: VerifyClientProtocol
+ verifyClient: VerifyClientProtocol,
+ verifyContextStore: CodableStore
) {
self.networkingInteractor = networkingInteractor
self.logger = logger
@@ -25,6 +28,7 @@ class WalletRequestSubscriber {
self.walletErrorResponder = walletErrorResponder
self.pairingRegisterer = pairingRegisterer
self.verifyClient = verifyClient
+ self.verifyContextStore = verifyContextStore
subscribeForRequest()
}
@@ -33,10 +37,7 @@ class WalletRequestSubscriber {
.sink { [unowned self] (payload: RequestSubscriptionPayload) in
logger.debug("WalletRequestSubscriber: Received request")
- pairingRegisterer.activate(
- pairingTopic: payload.topic,
- peerMetadata: payload.request.requester.metadata
- )
+ pairingRegisterer.setReceived(pairingTopic: payload.topic)
let request = AuthRequest(id: payload.id, topic: payload.topic, payload: payload.request.payloadParams)
@@ -45,9 +46,11 @@ class WalletRequestSubscriber {
do {
let origin = try await verifyClient.verifyOrigin(assertionId: assertionId)
let verifyContext = verifyClient.createVerifyContext(origin: origin, domain: payload.request.payloadParams.domain)
+ verifyContextStore.set(verifyContext, forKey: request.id.string)
onRequest?((request, verifyContext))
} catch {
let verifyContext = verifyClient.createVerifyContext(origin: nil, domain: payload.request.payloadParams.domain)
+ verifyContextStore.set(verifyContext, forKey: request.id.string)
onRequest?((request, verifyContext))
return
}
diff --git a/Sources/Auth/Services/Wallet/WalletRespondService.swift b/Sources/Auth/Services/Wallet/WalletRespondService.swift
index 62bb3427a..a06a16027 100644
--- a/Sources/Auth/Services/Wallet/WalletRespondService.swift
+++ b/Sources/Auth/Services/Wallet/WalletRespondService.swift
@@ -8,19 +8,27 @@ actor WalletRespondService {
private let networkingInteractor: NetworkInteracting
private let kms: KeyManagementService
private let rpcHistory: RPCHistory
+ private let verifyContextStore: CodableStore
private let logger: ConsoleLogging
private let walletErrorResponder: WalletErrorResponder
+ private let pairingRegisterer: PairingRegisterer
- init(networkingInteractor: NetworkInteracting,
- logger: ConsoleLogging,
- kms: KeyManagementService,
- rpcHistory: RPCHistory,
- walletErrorResponder: WalletErrorResponder) {
+ init(
+ networkingInteractor: NetworkInteracting,
+ logger: ConsoleLogging,
+ kms: KeyManagementService,
+ rpcHistory: RPCHistory,
+ verifyContextStore: CodableStore,
+ walletErrorResponder: WalletErrorResponder,
+ pairingRegisterer: PairingRegisterer
+ ) {
self.networkingInteractor = networkingInteractor
self.logger = logger
self.kms = kms
self.rpcHistory = rpcHistory
+ self.verifyContextStore = verifyContextStore
self.walletErrorResponder = walletErrorResponder
+ self.pairingRegisterer = pairingRegisterer
}
func respond(requestId: RPCID, signature: CacaoSignature, account: Account) async throws {
@@ -31,14 +39,22 @@ actor WalletRespondService {
let header = CacaoHeader(t: "eip4361")
let payload = try authRequestParams.payloadParams.cacaoPayload(address: account.address)
- let responseParams = AuthResponseParams(h: header, p: payload, s: signature)
+ let responseParams = AuthResponseParams(h: header, p: payload, s: signature)
let response = RPCResponse(id: requestId, result: responseParams)
try await networkingInteractor.respond(topic: topic, response: response, protocolMethod: AuthRequestProtocolMethod(), envelopeType: .type1(pubKey: keys.publicKey.rawRepresentation))
+
+ pairingRegisterer.activate(
+ pairingTopic: topic,
+ peerMetadata: authRequestParams.requester.metadata
+ )
+
+ verifyContextStore.delete(forKey: requestId.string)
}
func respondError(requestId: RPCID) async throws {
try await walletErrorResponder.respondError(AuthError.userRejeted, requestId: requestId)
+ verifyContextStore.delete(forKey: requestId.string)
}
private func getAuthRequestParams(requestId: RPCID) throws -> AuthRequestParams {
diff --git a/Sources/Auth/Types/Errors/AuthError.swift b/Sources/Auth/Types/Errors/AuthError.swift
index 7f9256ebd..cf4accb16 100644
--- a/Sources/Auth/Types/Errors/AuthError.swift
+++ b/Sources/Auth/Types/Errors/AuthError.swift
@@ -2,6 +2,7 @@ import Foundation
/// Authentication error
public enum AuthError: Codable, Equatable, Error {
+ case methodUnsupported
case userDisconnected
case userRejeted
case malformedResponseParams
@@ -14,6 +15,8 @@ extension AuthError: Reason {
init?(code: Int) {
switch code {
+ case Self.methodUnsupported.code:
+ self = .methodUnsupported
case Self.userRejeted.code:
self = .userRejeted
case Self.malformedResponseParams.code:
@@ -31,6 +34,8 @@ extension AuthError: Reason {
public var code: Int {
switch self {
+ case .methodUnsupported:
+ return 10001
case .userDisconnected:
return 6000
case .userRejeted:
@@ -48,6 +53,8 @@ extension AuthError: Reason {
public var message: String {
switch self {
+ case .methodUnsupported:
+ return "Method Unsupported"
case .userRejeted:
return "Auth request rejected by user"
case .malformedResponseParams:
diff --git a/Sources/Chat/ChatClientFactory.swift b/Sources/Chat/ChatClientFactory.swift
index 558b3f7b2..d8f336381 100644
--- a/Sources/Chat/ChatClientFactory.swift
+++ b/Sources/Chat/ChatClientFactory.swift
@@ -28,7 +28,7 @@ public struct ChatClientFactory {
historyClient: HistoryClient
) -> ChatClient {
let kms = KeyManagementService(keychain: keychain)
- let serializer = Serializer(kms: kms)
+ let serializer = Serializer(kms: kms, logger: logger)
let historyService = HistoryService(historyClient: historyClient, seiralizer: serializer)
let messageStore = KeyedDatabase(storage: storage, identifier: ChatStorageIdentifiers.messages.rawValue)
let receivedInviteStore = KeyedDatabase(storage: storage, identifier: ChatStorageIdentifiers.receivedInvites.rawValue)
diff --git a/Sources/Chat/Storage/ChatStorage.swift b/Sources/Chat/Storage/ChatStorage.swift
index 400536e9f..67ff84402 100644
--- a/Sources/Chat/Storage/ChatStorage.swift
+++ b/Sources/Chat/Storage/ChatStorage.swift
@@ -102,10 +102,15 @@ final class ChatStorage {
// MARK: - Configuration
func initializeStores(for account: Account) async throws {
- try await sentInviteStore.initialize(for: account)
- try await threadStore.initialize(for: account)
- try await inviteKeyStore.initialize(for: account)
- try await receivedInviteStatusStore.initialize(for: account)
+ try await sentInviteStore.create(for: account)
+ try await threadStore.create(for: account)
+ try await inviteKeyStore.create(for: account)
+ try await receivedInviteStatusStore.create(for: account)
+
+ try await sentInviteStore.subscribe(for: account)
+ try await threadStore.subscribe(for: account)
+ try await inviteKeyStore.subscribe(for: account)
+ try await receivedInviteStatusStore.subscribe(for: account)
}
func initializeDelegates() async throws {
@@ -132,9 +137,9 @@ final class ChatStorage {
receivedInvitesPublisherSubject.send(getReceivedInvites(account: account))
}
- try sentInviteStore.setupSubscriptions(account: account)
- try threadStore.setupSubscriptions(account: account)
- try inviteKeyStore.setupSubscriptions(account: account)
+ try sentInviteStore.setupDatabaseSubscriptions(account: account)
+ try threadStore.setupDatabaseSubscriptions(account: account)
+ try inviteKeyStore.setupDatabaseSubscriptions(account: account)
}
// MARK: - Invites
@@ -280,7 +285,7 @@ private extension ChatStorage {
func setupSyncSubscriptions() {
sentInviteStore.syncUpdatePublisher.sink { [unowned self] topic, account, update in
switch update {
- case .set(let object):
+ case .set(let object), .update(let object):
self.sentInviteStoreDelegate.onUpdate(object)
case .delete(let object):
self.sentInviteStoreDelegate.onDelete(object)
@@ -289,7 +294,7 @@ private extension ChatStorage {
threadStore.syncUpdatePublisher.sink { [unowned self] topic, account, update in
switch update {
- case .set(let object):
+ case .set(let object), .update(let object):
self.threadStoreDelegate.onUpdate(object, storage: self)
case .delete(let object):
self.threadStoreDelegate.onDelete(object)
@@ -298,7 +303,7 @@ private extension ChatStorage {
inviteKeyStore.syncUpdatePublisher.sink { [unowned self] topic, account, update in
switch update {
- case .set(let object):
+ case .set(let object), .update(let object):
self.inviteKeyDelegate.onUpdate(object, account: account)
case .delete(let object):
self.inviteKeyDelegate.onDelete(object)
@@ -307,7 +312,7 @@ private extension ChatStorage {
receivedInviteStatusStore.syncUpdatePublisher.sink { [unowned self] topic, account, update in
switch update {
- case .set(let object):
+ case .set(let object), .update(let object):
self.receiviedInviteStatusDelegate.onUpdate(object, storage: self, account: account)
case .delete(let object):
self.receiviedInviteStatusDelegate.onDelete(object)
diff --git a/Sources/Chat/Types/Payloads/AcceptPayload.swift b/Sources/Chat/Types/Payloads/AcceptPayload.swift
index 52c403c31..be06d1aa6 100644
--- a/Sources/Chat/Types/Payloads/AcceptPayload.swift
+++ b/Sources/Chat/Types/Payloads/AcceptPayload.swift
@@ -10,7 +10,11 @@ struct AcceptPayload: JWTClaimsCodable {
let aud: String // proposer/inviter blockchain account (did:pkh)
let sub: String // public key sent by the responder/invitee
- let act: String // description of action intent
+ let act: String? // description of action intent
+
+ static var action: String? {
+ return "invite_approval"
+ }
}
struct Wrapper: JWTWrapper {
@@ -49,7 +53,7 @@ struct AcceptPayload: JWTClaimsCodable {
ksu: keyserver.absoluteString,
aud: inviterAccount.did,
sub: inviteePublicKey.did(variant: .X25519),
- act: "invite_approval"
+ act: Claims.action
)
}
}
diff --git a/Sources/Chat/Types/Payloads/InvitePayload.swift b/Sources/Chat/Types/Payloads/InvitePayload.swift
index f33bea807..bd6fdef67 100644
--- a/Sources/Chat/Types/Payloads/InvitePayload.swift
+++ b/Sources/Chat/Types/Payloads/InvitePayload.swift
@@ -23,7 +23,11 @@ struct InvitePayload: JWTClaimsCodable {
let aud: String // responder/invitee blockchain account (did:pkh)
let sub: String // opening message included in the invite
let pke: String // proposer/inviter public key (did:key)
- let act: String // description of action intent
+ let act: String? // description of action intent
+
+ static var action: String? {
+ return "invite_proposal"
+ }
}
let keyserver: URL
@@ -54,7 +58,7 @@ struct InvitePayload: JWTClaimsCodable {
aud: inviteeAccount.did,
sub: message,
pke: inviterPublicKey.did(variant: .X25519),
- act: "invite_proposal"
+ act: Claims.action
)
}
}
diff --git a/Sources/Chat/Types/Payloads/MessagePayload.swift b/Sources/Chat/Types/Payloads/MessagePayload.swift
index bb0632d5d..445612dc9 100644
--- a/Sources/Chat/Types/Payloads/MessagePayload.swift
+++ b/Sources/Chat/Types/Payloads/MessagePayload.swift
@@ -10,10 +10,14 @@ struct MessagePayload: JWTClaimsCodable {
let aud: String // recipient blockchain account (did:pkh)
let sub: String // message sent by the author account
- let act: String // description of action intent
+ let act: String? // description of action intent
// TODO: Media not implemented
// public let xma: Media?
+
+ static var action: String? {
+ return "chat_message"
+ }
}
struct Wrapper: JWTWrapper {
@@ -52,7 +56,7 @@ struct MessagePayload: JWTClaimsCodable {
ksu: keyserver.absoluteString,
aud: DIDPKH(account: recipientAccount).string,
sub: message,
- act: "chat_message"
+ act: Claims.action
)
}
}
diff --git a/Sources/Chat/Types/Payloads/ReceiptPayload.swift b/Sources/Chat/Types/Payloads/ReceiptPayload.swift
index c5206922e..90362b305 100644
--- a/Sources/Chat/Types/Payloads/ReceiptPayload.swift
+++ b/Sources/Chat/Types/Payloads/ReceiptPayload.swift
@@ -10,7 +10,11 @@ struct ReceiptPayload: JWTClaimsCodable {
let sub: String // hash of the message received
let aud: String // sender blockchain account (did:pkh)
- let act: String // description of action intent
+ let act: String? // description of action intent
+
+ static var action: String? {
+ return "chat_receipt"
+ }
}
struct Wrapper: JWTWrapper {
@@ -49,7 +53,7 @@ struct ReceiptPayload: JWTClaimsCodable {
ksu: keyserver.absoluteString,
sub: messageHash,
aud: DIDPKH(account: senderAccount).string,
- act: "chat_receipt"
+ act: Claims.action
)
}
}
diff --git a/Sources/HTTPClient/HTTPClient.swift b/Sources/HTTPClient/HTTPClient.swift
index 0fb4c8a68..99eeca29a 100644
--- a/Sources/HTTPClient/HTTPClient.swift
+++ b/Sources/HTTPClient/HTTPClient.swift
@@ -3,4 +3,5 @@ import Foundation
public protocol HTTPClient {
func request(_ type: T.Type, at service: HTTPService) async throws -> T
func request(service: HTTPService) async throws
+ func updateHost(host: String) async
}
diff --git a/Sources/HTTPClient/HTTPError.swift b/Sources/HTTPClient/HTTPError.swift
index 446c8cbf2..959663bcc 100644
--- a/Sources/HTTPClient/HTTPError.swift
+++ b/Sources/HTTPClient/HTTPError.swift
@@ -1,10 +1,27 @@
import Foundation
-enum HTTPError: Error {
+public enum HTTPError: Error, Equatable {
case malformedURL(HTTPService)
+ case couldNotConnect
case dataTaskError(Error)
case noResponse
case badStatusCode(Int)
case responseDataNil
case jsonDecodeFailed(Error, Data)
+
+ public static func ==(lhs: HTTPError, rhs: HTTPError) -> Bool {
+ switch (lhs, rhs) {
+ case (.malformedURL, .malformedURL),
+ (.couldNotConnect, .couldNotConnect),
+ (.noResponse, .noResponse),
+ (.responseDataNil, .responseDataNil),
+ (.dataTaskError, .dataTaskError),
+ (.badStatusCode, .badStatusCode),
+ (.jsonDecodeFailed, .jsonDecodeFailed):
+ return true
+
+ default:
+ return false
+ }
+ }
}
diff --git a/Sources/HTTPClient/HTTPNetworkClient.swift b/Sources/HTTPClient/HTTPNetworkClient.swift
index 1cb5da106..a00ca2119 100644
--- a/Sources/HTTPClient/HTTPNetworkClient.swift
+++ b/Sources/HTTPClient/HTTPNetworkClient.swift
@@ -2,7 +2,7 @@ import Foundation
public actor HTTPNetworkClient: HTTPClient {
- let host: String
+ private var host: String
private let session: URLSession
@@ -31,6 +31,10 @@ public actor HTTPNetworkClient: HTTPClient {
}
}
}
+
+ public func updateHost(host: String) async {
+ self.host = host
+ }
private func request(_ type: T.Type, at service: HTTPService, completion: @escaping (Result) -> Void) {
guard let request = service.resolve(for: host) else {
@@ -67,6 +71,9 @@ public actor HTTPNetworkClient: HTTPClient {
}
private static func validate(_ urlResponse: URLResponse?, _ error: Error?) throws {
+ if let error = (error as? NSError), error.code == -1004 {
+ throw HTTPError.couldNotConnect
+ }
if let error = error {
throw HTTPError.dataTaskError(error)
}
diff --git a/Sources/WalletConnectEcho/Echo.swift b/Sources/WalletConnectEcho/Echo.swift
deleted file mode 100644
index 702033cb5..000000000
--- a/Sources/WalletConnectEcho/Echo.swift
+++ /dev/null
@@ -1,28 +0,0 @@
-import Foundation
-
-public class Echo {
- static public let echoHost = "echo.walletconnect.com"
- public static var instance: EchoClient = {
- guard let config = Echo.config else {
- fatalError("Error - you must call Echo.configure(_:) before accessing the shared instance.")
- }
-
- return EchoClientFactory.create(
- projectId: Networking.projectId,
- echoHost: config.echoHost,
- environment: config.environment)
- }()
-
- private static var config: Config?
-
- private init() { }
-
- /// Echo instance config method
- /// - Parameter clientId: https://github.com/WalletConnect/walletconnect-docs/blob/main/docs/specs/clients/core/relay/relay-client-auth.md#overview
- static public func configure(
- echoHost: String = echoHost,
- environment: APNSEnvironment
- ) {
- Echo.config = Echo.Config(echoHost: echoHost, environment: environment)
- }
-}
diff --git a/Sources/WalletConnectEcho/EchoClient.swift b/Sources/WalletConnectEcho/EchoClient.swift
deleted file mode 100644
index d7724ec5a..000000000
--- a/Sources/WalletConnectEcho/EchoClient.swift
+++ /dev/null
@@ -1,19 +0,0 @@
-import Foundation
-
-public class EchoClient: EchoClientProtocol {
- private let registerService: EchoRegisterService
-
- init(registerService: EchoRegisterService) {
- self.registerService = registerService
- }
-
- public func register(deviceToken: Data) async throws {
- try await registerService.register(deviceToken: deviceToken)
- }
-
-#if DEBUG
- public func register(deviceToken: String) async throws {
- try await registerService.register(deviceToken: deviceToken)
- }
-#endif
-}
diff --git a/Sources/WalletConnectEcho/EchoClientFactory.swift b/Sources/WalletConnectEcho/EchoClientFactory.swift
deleted file mode 100644
index 563aeef9d..000000000
--- a/Sources/WalletConnectEcho/EchoClientFactory.swift
+++ /dev/null
@@ -1,35 +0,0 @@
-import Foundation
-
-public struct EchoClientFactory {
- public static func create(projectId: String,
- echoHost: String,
- environment: APNSEnvironment) -> EchoClient {
-
- let keychainStorage = KeychainStorage(serviceIdentifier: "com.walletconnect.sdk")
-
- return EchoClientFactory.create(
- projectId: projectId,
- echoHost: echoHost,
- keychainStorage: keychainStorage,
- environment: environment)
- }
-
- public static func create(projectId: String,
- echoHost: String,
- keychainStorage: KeychainStorageProtocol,
- environment: APNSEnvironment) -> EchoClient {
-
- let httpClient = HTTPNetworkClient(host: echoHost)
-
- let clientIdStorage = ClientIdStorage(keychain: keychainStorage)
-
- let echoAuthenticator = EchoAuthenticator(clientIdStorage: clientIdStorage, echoHost: echoHost)
-
- let logger = ConsoleLogger(loggingLevel: .debug)
-
- let registerService = EchoRegisterService(httpClient: httpClient, projectId: projectId, clientIdStorage: clientIdStorage, echoAuthenticator: echoAuthenticator, logger: logger, environment: environment)
-
- return EchoClient(
- registerService: registerService)
- }
-}
diff --git a/Sources/WalletConnectEcho/EchoImports.swift b/Sources/WalletConnectEcho/EchoImports.swift
deleted file mode 100644
index 463cb7c23..000000000
--- a/Sources/WalletConnectEcho/EchoImports.swift
+++ /dev/null
@@ -1,4 +0,0 @@
-#if !CocoaPods
-@_exported import WalletConnectNetworking
-@_exported import WalletConnectJWT
-#endif
diff --git a/Sources/WalletConnectEcho/Register/EchoRegisterService.swift b/Sources/WalletConnectEcho/Register/EchoRegisterService.swift
deleted file mode 100644
index b3df306ce..000000000
--- a/Sources/WalletConnectEcho/Register/EchoRegisterService.swift
+++ /dev/null
@@ -1,62 +0,0 @@
-import Foundation
-
-actor EchoRegisterService {
- private let httpClient: HTTPClient
- private let projectId: String
- private let logger: ConsoleLogging
- private let environment: APNSEnvironment
- private let echoAuthenticator: EchoAuthenticating
- private let clientIdStorage: ClientIdStoring
-
- enum Errors: Error {
- case registrationFailed
- }
-
- init(httpClient: HTTPClient,
- projectId: String,
- clientIdStorage: ClientIdStoring,
- echoAuthenticator: EchoAuthenticating,
- logger: ConsoleLogging,
- environment: APNSEnvironment) {
- self.httpClient = httpClient
- self.clientIdStorage = clientIdStorage
- self.echoAuthenticator = echoAuthenticator
- self.projectId = projectId
- self.logger = logger
- self.environment = environment
- }
-
- func register(deviceToken: Data) async throws {
- let tokenParts = deviceToken.map { data in String(format: "%02.2hhx", data) }
- let token = tokenParts.joined()
- let echoAuthToken = try echoAuthenticator.createAuthToken()
- let clientId = try clientIdStorage.getClientId()
- let clientIdMutlibase = try DIDKey(did: clientId).multibase(variant: .ED25519)
- logger.debug("APNS device token: \(token)")
- let response = try await httpClient.request(
- EchoResponse.self,
- at: EchoAPI.register(clientId: clientIdMutlibase, token: token, projectId: projectId, environment: environment, auth: echoAuthToken)
- )
- guard response.status == .success else {
- throw Errors.registrationFailed
- }
- logger.debug("Successfully registered at Echo Server")
- }
-
-#if DEBUG
- public func register(deviceToken: String) async throws {
- let echoAuthToken = try echoAuthenticator.createAuthToken()
- let clientId = try clientIdStorage.getClientId()
- let clientIdMutlibase = try DIDKey(did: clientId).multibase(variant: .ED25519)
- let response = try await httpClient.request(
- EchoResponse.self,
- at: EchoAPI.register(clientId: clientIdMutlibase, token: deviceToken, projectId: projectId, environment: environment, auth: echoAuthToken)
- )
- guard response.status == .success else {
- throw Errors.registrationFailed
- }
- logger.debug("Successfully registered at Echo Server")
- }
-#endif
-}
-
diff --git a/Sources/WalletConnectHistory/HistoryClientFactory.swift b/Sources/WalletConnectHistory/HistoryClientFactory.swift
index c328ff4eb..5168430a3 100644
--- a/Sources/WalletConnectHistory/HistoryClientFactory.swift
+++ b/Sources/WalletConnectHistory/HistoryClientFactory.swift
@@ -14,7 +14,7 @@ class HistoryClientFactory {
static func create(historyUrl: String, relayUrl: String, keychain: KeychainStorageProtocol) -> HistoryClient {
let clientIdStorage = ClientIdStorage(keychain: keychain)
let kms = KeyManagementService(keychain: keychain)
- let serializer = Serializer(kms: kms)
+ let serializer = Serializer(kms: kms, logger: ConsoleLogger(prefix: "🔐", loggingLevel: .off))
let historyNetworkService = HistoryNetworkService(clientIdStorage: clientIdStorage)
return HistoryClient(
historyUrl: historyUrl,
diff --git a/Sources/WalletConnectIdentity/IdentityClient.swift b/Sources/WalletConnectIdentity/IdentityClient.swift
index 8ec6d2943..57335fb79 100644
--- a/Sources/WalletConnectIdentity/IdentityClient.swift
+++ b/Sources/WalletConnectIdentity/IdentityClient.swift
@@ -72,4 +72,9 @@ public final class IdentityClient {
public func getInviteKey(for account: Account) throws -> AgreementPublicKey {
return try identityStorage.getInviteKey(for: account)
}
+
+ public func isIdentityRegistered(account: Account) -> Bool {
+ let key = try? identityStorage.getIdentityKey(for: account)
+ return key != nil
+ }
}
diff --git a/Sources/WalletConnectIdentity/IdentityService.swift b/Sources/WalletConnectIdentity/IdentityService.swift
index 8a1a984a8..5f7fc0431 100644
--- a/Sources/WalletConnectIdentity/IdentityService.swift
+++ b/Sources/WalletConnectIdentity/IdentityService.swift
@@ -48,7 +48,7 @@ actor IdentityService {
let inviteKey = try kms.createX25519KeyPair()
let invitePublicKey = DIDKey(rawData: inviteKey.rawRepresentation)
- let idAuth = try makeIDAuth(account: account, issuer: invitePublicKey, kind: .registerInvite)
+ let idAuth = try makeIDAuth(account: account, issuer: invitePublicKey, claims: RegisterInviteClaims.self)
try await networkService.registerInvite(idAuth: idAuth)
return try storage.saveInviteKey(inviteKey, for: account)
@@ -57,7 +57,7 @@ actor IdentityService {
func unregister(account: Account, onSign: SigningCallback) async throws {
let identityKey = try storage.getIdentityKey(for: account)
let identityPublicKey = DIDKey(rawData: identityKey.publicKey.rawRepresentation)
- let idAuth = try makeIDAuth(account: account, issuer: identityPublicKey, kind: .unregisterIdentity)
+ let idAuth = try makeIDAuth(account: account, issuer: identityPublicKey, claims: UnregisterIdentityClaims.self)
try await networkService.removeIdentity(idAuth: idAuth)
try storage.removeIdentityKey(for: account)
}
@@ -65,7 +65,7 @@ actor IdentityService {
func goPrivate(account: Account) async throws -> AgreementPublicKey {
let inviteKey = try storage.getInviteKey(for: account)
let invitePublicKey = DIDKey(rawData: inviteKey.rawRepresentation)
- let idAuth = try makeIDAuth(account: account, issuer: invitePublicKey, kind: .unregisterInvite)
+ let idAuth = try makeIDAuth(account: account, issuer: invitePublicKey, claims: UnregisterInviteClaims.self)
try await networkService.removeInvite(idAuth: idAuth)
try storage.removeInviteKey(for: account)
@@ -113,11 +113,10 @@ private extension IdentityService {
}
}
- func makeIDAuth(account: Account, issuer: DIDKey, kind: IDAuthPayload.Kind) throws -> String {
+ func makeIDAuth(account: Account, issuer: DIDKey, claims: Claims.Type) throws -> String {
let identityKey = try storage.getIdentityKey(for: account)
- let payload = IDAuthPayload(
- kind: kind,
+ let payload = IDAuthPayload(
keyserver: keyserverURL,
account: account,
invitePublicKey: issuer
diff --git a/Sources/WalletConnectIdentity/Types/IDAuthClaims.swift b/Sources/WalletConnectIdentity/Types/IDAuthClaims.swift
new file mode 100644
index 000000000..55c47a7df
--- /dev/null
+++ b/Sources/WalletConnectIdentity/Types/IDAuthClaims.swift
@@ -0,0 +1,13 @@
+import Foundation
+
+protocol IDAuthClaims: JWTClaims {
+ var iss: String { get }
+ var sub: String { get }
+ var aud: String { get }
+ var iat: UInt64 { get }
+ var exp: UInt64 { get }
+ var pkh: String { get }
+ var act: String? { get }
+
+ init(iss: String, sub: String, aud: String, iat: UInt64, exp: UInt64, pkh: String, act: String?)
+}
diff --git a/Sources/WalletConnectIdentity/Types/IDAuthPayload.swift b/Sources/WalletConnectIdentity/Types/IDAuthPayload.swift
index f8dcc7226..6b418538f 100644
--- a/Sources/WalletConnectIdentity/Types/IDAuthPayload.swift
+++ b/Sources/WalletConnectIdentity/Types/IDAuthPayload.swift
@@ -1,52 +1,26 @@
import Foundation
-struct IDAuthPayload: JWTClaimsCodable {
+struct IDAuthPayload: JWTClaimsCodable {
enum Errors: Error {
case undefinedKind
}
- enum Kind: String {
- case registerInvite = "register_invite"
- case unregisterInvite = "unregister_invite"
- case unregisterIdentity = "unregister_identity"
-
- init(rawValue: String) throws {
- guard let kind = Kind(rawValue: rawValue) else {
- throw Errors.undefinedKind
- }
- self = kind
- }
- }
-
struct Wrapper: JWTWrapper {
let jwtString: String
}
- struct Claims: JWTClaims {
- let iss: String
- let sub: String
- let aud: String
- let iat: UInt64
- let exp: UInt64
- let pkh: String
- let act: String
- }
-
- let kind: Kind
let keyserver: URL
let account: Account
let invitePublicKey: DIDKey
- init(kind: Kind, keyserver: URL, account: Account, invitePublicKey: DIDKey) {
- self.kind = kind
+ init(keyserver: URL, account: Account, invitePublicKey: DIDKey) {
self.keyserver = keyserver
self.account = account
self.invitePublicKey = invitePublicKey
}
init(claims: Claims) throws {
- self.kind = try Kind(rawValue: claims.act)
self.keyserver = try claims.aud.asURL()
self.account = try Account(DIDPKHString: claims.pkh)
self.invitePublicKey = try DIDKey(did: claims.sub)
@@ -60,7 +34,7 @@ struct IDAuthPayload: JWTClaimsCodable {
iat: defaultIatMilliseconds(),
exp: expiry(days: 30),
pkh: account.did,
- act: kind.rawValue
+ act: Claims.action
)
}
}
diff --git a/Sources/WalletConnectIdentity/Types/RegisterInviteClaims.swift b/Sources/WalletConnectIdentity/Types/RegisterInviteClaims.swift
new file mode 100644
index 000000000..2a32ea8c3
--- /dev/null
+++ b/Sources/WalletConnectIdentity/Types/RegisterInviteClaims.swift
@@ -0,0 +1,15 @@
+import Foundation
+
+struct RegisterInviteClaims: IDAuthClaims {
+ let iss: String
+ let sub: String
+ let aud: String
+ let iat: UInt64
+ let exp: UInt64
+ let pkh: String
+ let act: String?
+
+ static var action: String? {
+ return "register_invite"
+ }
+}
diff --git a/Sources/WalletConnectIdentity/Types/UnregisterIdentityClaims.swift b/Sources/WalletConnectIdentity/Types/UnregisterIdentityClaims.swift
new file mode 100644
index 000000000..881c988ba
--- /dev/null
+++ b/Sources/WalletConnectIdentity/Types/UnregisterIdentityClaims.swift
@@ -0,0 +1,15 @@
+import Foundation
+
+struct UnregisterIdentityClaims: IDAuthClaims {
+ let iss: String
+ let sub: String
+ let aud: String
+ let iat: UInt64
+ let exp: UInt64
+ let pkh: String
+ let act: String?
+
+ static var action: String? {
+ return "unregister_identity"
+ }
+}
diff --git a/Sources/WalletConnectIdentity/Types/UnregisterInviteClaims.swift b/Sources/WalletConnectIdentity/Types/UnregisterInviteClaims.swift
new file mode 100644
index 000000000..9b3452051
--- /dev/null
+++ b/Sources/WalletConnectIdentity/Types/UnregisterInviteClaims.swift
@@ -0,0 +1,15 @@
+import Foundation
+
+struct UnregisterInviteClaims: IDAuthClaims {
+ let iss: String
+ let sub: String
+ let aud: String
+ let iat: UInt64
+ let exp: UInt64
+ let pkh: String
+ let act: String?
+
+ static var action: String? {
+ return "unregister_invite"
+ }
+}
diff --git a/Sources/WalletConnectJWT/JWTDecodable.swift b/Sources/WalletConnectJWT/JWTDecodable.swift
index 59b1871b1..2f96ae072 100644
--- a/Sources/WalletConnectJWT/JWTDecodable.swift
+++ b/Sources/WalletConnectJWT/JWTDecodable.swift
@@ -10,6 +10,9 @@ public protocol JWTClaims: JWTEncodable {
var iss: String { get }
var iat: UInt64 { get }
var exp: UInt64 { get }
+ var act: String? { get }
+
+ static var action: String? { get }
}
public protocol JWTClaimsCodable {
@@ -32,6 +35,9 @@ extension JWTClaimsCodable {
guard try JWTValidator(jwtString: wrapper.jwtString).isValid(publicKey: signingPublicKey)
else { throw JWTError.signatureVerificationFailed }
+ guard Claims.action == jwt.claims.act
+ else { throw JWTError.actMismatch }
+
return (try Self.init(claims: jwt.claims), jwt.claims)
}
diff --git a/Sources/WalletConnectJWT/JWTError.swift b/Sources/WalletConnectJWT/JWTError.swift
index 0d84e2b5f..90cc40e14 100644
--- a/Sources/WalletConnectJWT/JWTError.swift
+++ b/Sources/WalletConnectJWT/JWTError.swift
@@ -7,4 +7,5 @@ enum JWTError: Error {
case noSignature
case invalidJWTString
case signatureVerificationFailed
+ case actMismatch
}
diff --git a/Sources/WalletConnectKMS/Serialiser/Serializer.swift b/Sources/WalletConnectKMS/Serialiser/Serializer.swift
index dcd8f984e..7a3dcf739 100644
--- a/Sources/WalletConnectKMS/Serialiser/Serializer.swift
+++ b/Sources/WalletConnectKMS/Serialiser/Serializer.swift
@@ -1,23 +1,43 @@
import Foundation
+import Combine
public class Serializer: Serializing {
- enum Errors: String, Error {
- case symmetricKeyForTopicNotFound
+ enum Errors: Error, CustomStringConvertible {
+ case symmetricKeyForTopicNotFound(String)
case publicKeyForTopicNotFound
+
+ var description: String {
+ switch self {
+ case .symmetricKeyForTopicNotFound(let topic):
+ return "Error: Symmetric key for topic '\(topic)' was not found."
+ case .publicKeyForTopicNotFound:
+ return "Error: Public key for topic was not found."
+ }
+ }
}
private let kms: KeyManagementServiceProtocol
private let codec: Codec
+ private let logger: ConsoleLogging
+ public var logsPublisher: AnyPublisher {
+ logger.logsPublisher.eraseToAnyPublisher()
+ }
- init(kms: KeyManagementServiceProtocol, codec: Codec = ChaChaPolyCodec()) {
+ init(kms: KeyManagementServiceProtocol, codec: Codec = ChaChaPolyCodec(), logger: ConsoleLogging) {
self.kms = kms
self.codec = codec
+ self.logger = logger
}
- public init(kms: KeyManagementServiceProtocol) {
+ public init(kms: KeyManagementServiceProtocol, logger: ConsoleLogging) {
self.kms = kms
self.codec = ChaChaPolyCodec()
+ self.logger = logger
+ }
+
+ public func setLogging(level: LoggingLevel) {
+ logger.setLogging(level: level)
}
/// Encrypts and serializes an object
@@ -29,7 +49,9 @@ public class Serializer: Serializing {
public func serialize(topic: String, encodable: Encodable, envelopeType: Envelope.EnvelopeType) throws -> String {
let messageJson = try encodable.json()
guard let symmetricKey = kms.getSymmetricKeyRepresentable(for: topic) else {
- throw Errors.symmetricKeyForTopicNotFound
+ let error = Errors.symmetricKeyForTopicNotFound(topic)
+ logger.error("\(error)")
+ throw error
}
let sealbox = try codec.encode(plaintext: messageJson, symmetricKey: symmetricKey)
return Envelope(type: envelopeType, sealbox: sealbox).serialised()
@@ -53,15 +75,29 @@ public class Serializer: Serializing {
private func handleType0Envelope(_ topic: String, _ envelope: Envelope) throws -> (T, Data) {
if let symmetricKey = kms.getSymmetricKeyRepresentable(for: topic) {
- return try decode(sealbox: envelope.sealbox, symmetricKey: symmetricKey)
+ do {
+ let decoded: (T, Data) = try decode(sealbox: envelope.sealbox, symmetricKey: symmetricKey)
+ logger.debug("Decoded: \(decoded.0)")
+ return decoded
+ }
+ catch {
+ logger.error("\(error)")
+ throw error
+ }
} else {
- throw Errors.symmetricKeyForTopicNotFound
+ let error = Errors.symmetricKeyForTopicNotFound(topic)
+ logger.error("\(error)")
+ throw error
}
}
private func handleType1Envelope(_ topic: String, peerPubKey: Data, sealbox: Data) throws -> (T, String, Data) {
guard let selfPubKey = kms.getPublicKey(for: topic)
- else { throw Errors.publicKeyForTopicNotFound }
+ else {
+ let error = Errors.publicKeyForTopicNotFound
+ logger.error("\(error)")
+ throw error
+ }
let agreementKeys = try kms.performKeyAgreement(selfPublicKey: selfPubKey, peerPublicKey: peerPubKey.toHexString())
let decodedType: (object: T, data: Data) = try decode(sealbox: sealbox, symmetricKey: agreementKeys.sharedKey.rawRepresentation)
@@ -71,7 +107,13 @@ public class Serializer: Serializing {
}
private func decode(sealbox: Data, symmetricKey: Data) throws -> (T, Data) {
- let decryptedData = try codec.decode(sealbox: sealbox, symmetricKey: symmetricKey)
- return (try JSONDecoder().decode(T.self, from: decryptedData), decryptedData)
+ do {
+ let decryptedData = try codec.decode(sealbox: sealbox, symmetricKey: symmetricKey)
+ let decodedType = try JSONDecoder().decode(T.self, from: decryptedData)
+ return (decodedType, decryptedData)
+ } catch {
+ logger.error("Failed to decode with error: \(error)")
+ throw error
+ }
}
}
diff --git a/Sources/WalletConnectKMS/Serialiser/Serializing.swift b/Sources/WalletConnectKMS/Serialiser/Serializing.swift
index 5982ce660..c9fc25cd2 100644
--- a/Sources/WalletConnectKMS/Serialiser/Serializing.swift
+++ b/Sources/WalletConnectKMS/Serialiser/Serializing.swift
@@ -1,6 +1,9 @@
import Foundation
+import Combine
public protocol Serializing {
+ var logsPublisher: AnyPublisher {get}
+ func setLogging(level: LoggingLevel)
func serialize(topic: String, encodable: Encodable, envelopeType: Envelope.EnvelopeType) throws -> String
/// - derivedTopic: topic derived from symmetric key as a result of key exchange if peers has sent envelope(type1) prefixed with it's public key
func deserialize(topic: String, encodedEnvelope: String) throws -> (T, derivedTopic: String?, decryptedPayload: Data)
diff --git a/Sources/WalletConnectModal/Mocks/Listing+Mocks.swift b/Sources/WalletConnectModal/Mocks/Listing+Mocks.swift
index 138b3c84a..b6f33a2a1 100644
--- a/Sources/WalletConnectModal/Mocks/Listing+Mocks.swift
+++ b/Sources/WalletConnectModal/Mocks/Listing+Mocks.swift
@@ -2,8 +2,62 @@ import Foundation
extension Listing {
static let stubList: [Listing] = [
- Listing(id: UUID().uuidString, name: "Sample Wallet", homepage: "https://example.com", order: 1, imageId: UUID().uuidString, app: Listing.App(ios: "https://example.com/download-ios", mac: "https://example.com/download-mac", safari: "https://example.com/download-safari"), mobile: Listing.Mobile(native: "sampleapp://deeplink", universal: "https://example.com/universal")),
- Listing(id: UUID().uuidString, name: "Awesome Wallet", homepage: "https://example.com/awesome", order: 2, imageId: UUID().uuidString, app: Listing.App(ios: "https://example.com/download-ios", mac: "https://example.com/download-mac", safari: "https://example.com/download-safari"), mobile: Listing.Mobile(native: "awesomeapp://deeplink", universal: "https://example.com/awesome/universal")),
- Listing(id: UUID().uuidString, name: "Cool Wallet", homepage: "https://example.com/cool", order: 3, imageId: UUID().uuidString, app: Listing.App(ios: "https://example.com/download-ios", mac: "https://example.com/download-mac", safari: "https://example.com/download-safari"), mobile: Listing.Mobile(native: "coolapp://deeplink", universal: "https://example.com/cool/universal"))
+ Listing(
+ id: UUID().uuidString,
+ name: "Sample Wallet",
+ homepage: "https://example.com",
+ order: 1,
+ imageId: UUID().uuidString,
+ app: Listing.App(
+ ios: "https://example.com/download-ios",
+ browser: "https://example.com/download-safari"
+ ),
+ mobile: .init(
+ native: "sampleapp://deeplink",
+ universal: "https://example.com/universal"
+ ),
+ desktop: .init(
+ native: nil,
+ universal: "https://example.com/universal"
+ )
+ ),
+ Listing(
+ id: UUID().uuidString,
+ name: "Awesome Wallet",
+ homepage: "https://example.com/awesome",
+ order: 2,
+ imageId: UUID().uuidString,
+ app: Listing.App(
+ ios: "https://example.com/download-ios",
+ browser: "https://example.com/download-safari"
+ ),
+ mobile: .init(
+ native: "awesomeapp://deeplink",
+ universal: "https://example.com/awesome/universal"
+ ),
+ desktop: .init(
+ native: nil,
+ universal: "https://example.com/awesome/universal"
+ )
+ ),
+ Listing(
+ id: UUID().uuidString,
+ name: "Cool Wallet",
+ homepage: "https://example.com/cool",
+ order: 3,
+ imageId: UUID().uuidString,
+ app: Listing.App(
+ ios: "https://example.com/download-ios",
+ browser: "https://example.com/download-safari"
+ ),
+ mobile: .init(
+ native: "coolapp://deeplink",
+ universal: "https://example.com/cool/universal"
+ ),
+ desktop: .init(
+ native: nil,
+ universal: "https://example.com/cool/universal"
+ )
+ )
]
}
diff --git a/Sources/WalletConnectModal/Modal/ModalSheet.swift b/Sources/WalletConnectModal/Modal/ModalSheet.swift
index 3abe3146d..c51e69d2b 100644
--- a/Sources/WalletConnectModal/Modal/ModalSheet.swift
+++ b/Sources/WalletConnectModal/Modal/ModalSheet.swift
@@ -14,7 +14,6 @@ public struct ModalSheet: View {
VStack(spacing: 0) {
contentHeader()
content()
-
}
.frame(maxWidth: .infinity)
.background(Color.background1)
@@ -75,13 +74,12 @@ public struct ModalSheet: View {
EmptyView()
}
}
- .animation(.default)
+ .animation(.default, value: viewModel.destination)
.foregroundColor(.accent)
.frame(height: 60)
.overlay(
VStack {
if viewModel.destination.hasSearch {
-
HStack {
Image(systemName: "magnifyingglass")
TextField("Search", text: $viewModel.searchTerm, onEditingChanged: { editing in
@@ -128,7 +126,7 @@ public struct ModalSheet: View {
viewModel.destination
}, set: { _ in }),
navigateTo: viewModel.navigateTo(_:),
- onListingTap: { viewModel.onListingTap($0, preferUniversal: false) }
+ onListingTap: { viewModel.onListingTap($0) }
)
}
@@ -144,7 +142,6 @@ public struct ModalSheet: View {
@ViewBuilder
private func content() -> some View {
-
switch viewModel.destination {
case .welcome,
.viewAll:
@@ -155,7 +152,7 @@ public struct ModalSheet: View {
case .getWallet:
GetAWalletView(
wallets: Array(viewModel.wallets.prefix(6)),
- onWalletTap: viewModel.onGetWalletTap(_:),
+ onWalletTap: viewModel.openAppstore(wallet:),
navigateToExternalLink: viewModel.navigateToExternalLink(_:)
)
.frame(minHeight: verticalSizeClass == .compact ? 200 : 550)
@@ -163,10 +160,10 @@ public struct ModalSheet: View {
case let .walletDetail(wallet):
WalletDetail(
- wallet: wallet,
- deeplink: { viewModel.onListingTap($0, preferUniversal: false) },
- deeplinkUniversal: { viewModel.onListingTap($0, preferUniversal: true) },
- openAppStore: viewModel.onGetWalletTap(_:)
+ viewModel: .init(
+ wallet: wallet,
+ deeplinkHandler: viewModel
+ )
)
}
}
@@ -177,7 +174,7 @@ extension ModalSheet {
Button {
viewModel.onCloseButton()
} label: {
- Image(.close)
+ Image(Asset.close)
.padding(8)
}
.buttonStyle(CircuralIconButtonStyle())
diff --git a/Sources/WalletConnectModal/Modal/ModalViewModel.swift b/Sources/WalletConnectModal/Modal/ModalViewModel.swift
index bdd5e9a9f..5c274468a 100644
--- a/Sources/WalletConnectModal/Modal/ModalViewModel.swift
+++ b/Sources/WalletConnectModal/Modal/ModalViewModel.swift
@@ -38,6 +38,7 @@ final class ModalViewModel: ObservableObject {
var isShown: Binding
let interactor: ModalSheetInteractor
let uiApplicationWrapper: UIApplicationWrapper
+ let recentWalletStorage: RecentWalletsStorage
@Published private(set) var destinationStack: [Destination] = [.welcome]
@Published private(set) var uri: String?
@@ -52,11 +53,9 @@ final class ModalViewModel: ObservableObject {
}
var filteredWallets: [Listing] {
- if searchTerm.isEmpty { return sortByRecent(wallets) }
-
- return sortByRecent(
- wallets.filter { $0.name.lowercased().contains(searchTerm.lowercased()) }
- )
+ wallets
+ .sortByRecent()
+ .filter(searchTerm: searchTerm)
}
private var disposeBag = Set()
@@ -65,11 +64,13 @@ final class ModalViewModel: ObservableObject {
init(
isShown: Binding,
interactor: ModalSheetInteractor,
- uiApplicationWrapper: UIApplicationWrapper = .live
+ uiApplicationWrapper: UIApplicationWrapper = .live,
+ recentWalletStorage: RecentWalletsStorage = RecentWalletsStorage()
) {
self.isShown = isShown
self.interactor = interactor
self.uiApplicationWrapper = uiApplicationWrapper
+ self.recentWalletStorage = recentWalletStorage
interactor.sessionSettlePublisher
.receive(on: DispatchQueue.main)
@@ -82,7 +83,7 @@ final class ModalViewModel: ObservableObject {
interactor.sessionRejectionPublisher
.receive(on: DispatchQueue.main)
- .sink { (proposal, reason) in
+ .sink { _, reason in
print(reason)
self.toast = Toast(style: .error, message: reason.message)
@@ -118,28 +119,16 @@ final class ModalViewModel: ObservableObject {
uiApplicationWrapper.openURL(url, nil)
}
- func onListingTap(_ listing: Listing, preferUniversal: Bool) {
+ func onListingTap(_ listing: Listing) {
setLastTimeUsed(listing.id)
-
- navigateToDeepLink(
- universalLink: listing.mobile.universal ?? "",
- nativeLink: listing.mobile.native ?? "",
- preferUniversal: preferUniversal
- )
- }
-
- func onGetWalletTap(_ listing: Listing) {
- guard
- let storeLinkString = listing.app.ios,
- let storeLink = URL(string: storeLinkString)
- else { return }
-
- uiApplicationWrapper.openURL(storeLink, nil)
}
func onBackButton() {
guard destinationStack.count != 1 else { return }
- _ = destinationStack.popLast()
+
+ withAnimation {
+ _ = destinationStack.popLast()
+ }
if destinationStack.last?.hasSearch == false {
searchTerm = ""
@@ -147,7 +136,6 @@ final class ModalViewModel: ObservableObject {
}
func onCopyButton() {
-
guard let uri else {
toast = Toast(style: .error, message: "No uri found")
return
@@ -179,106 +167,130 @@ final class ModalViewModel: ObservableObject {
// Small deliberate delay to ensure animations execute properly
try await Task.sleep(nanoseconds: 500_000_000)
- withAnimation {
- self.wallets = wallets.sorted {
- guard let lhs = $0.order else {
- return false
- }
-
- guard let rhs = $1.order else {
- return true
- }
-
- return lhs < rhs
- }
-
- loadRecentWallets()
- }
+ loadRecentWallets()
+ checkWhetherInstalled(wallets: wallets)
+
+ self.wallets = wallets
+ .sortByOrder()
+ .sortByInstalled()
} catch {
toast = Toast(style: .error, message: error.localizedDescription)
}
}
}
-// MARK: - Recent Wallets
+// MARK: - Sorting and filtering
-private extension ModalViewModel {
-
- func sortByRecent(_ input: [Listing]) -> [Listing] {
- input.sorted { lhs, rhs in
- guard let lhsLastTimeUsed = lhs.lastTimeUsed else {
+private extension Array where Element: Listing {
+ func sortByOrder() -> [Listing] {
+ sorted {
+ guard let lhs = $0.order else {
return false
}
- guard let rhsLastTimeUsed = rhs.lastTimeUsed else {
+ guard let rhs = $1.order else {
return true
}
- return lhsLastTimeUsed > rhsLastTimeUsed
+ return lhs < rhs
}
}
- func loadRecentWallets() {
- RecentWalletsStorage().recentWallets.forEach { wallet in
-
- guard let lastTimeUsed = wallet.lastTimeUsed else {
- return
+ func sortByInstalled() -> [Listing] {
+ sorted { lhs, rhs in
+ if lhs.installed, !rhs.installed {
+ return true
}
- // Consider Recent only for 3 days
- if abs(lastTimeUsed.timeIntervalSinceNow) > (24 * 60 * 60 * 3) {
- return
+ if !lhs.installed, rhs.installed {
+ return false
}
- setLastTimeUsed(wallet.id, date: lastTimeUsed)
+ return false
}
}
- func saveRecentWallets() {
- RecentWalletsStorage().recentWallets = Array(wallets.filter {
- $0.lastTimeUsed != nil
- }.prefix(5))
+ func sortByRecent() -> [Listing] {
+ sorted { lhs, rhs in
+ guard let lhsLastTimeUsed = lhs.lastTimeUsed else {
+ return false
+ }
+
+ guard let rhsLastTimeUsed = rhs.lastTimeUsed else {
+ return true
+ }
+
+ return lhsLastTimeUsed > rhsLastTimeUsed
+ }
}
- func setLastTimeUsed(_ walletId: String, date: Date = Date()) {
- guard let index = wallets.firstIndex(where: {
- $0.id == walletId
- }) else {
- return
- }
-
- var copy = wallets[index]
- copy.lastTimeUsed = date
- wallets[index] = copy
+ func filter(searchTerm: String) -> [Listing] {
+ if searchTerm.isEmpty { return self }
- saveRecentWallets()
+ return filter {
+ $0.name.lowercased().contains(searchTerm.lowercased())
+ }
}
}
-// MARK: - Deeplinking
+// MARK: - Recent & Installed Wallets
private extension ModalViewModel {
- enum DeeplinkErrors: LocalizedError {
- case noWalletLinkFound
- case uriNotCreated
- case failedToOpen
+ func checkWhetherInstalled(wallets: [Listing]) {
+ guard let schemes = Bundle.main.object(forInfoDictionaryKey: "LSApplicationQueriesSchemes") as? [String] else {
+ return
+ }
- var errorDescription: String? {
- switch self {
- case .noWalletLinkFound:
- return NSLocalizedString("No valid link for opening given wallet found", comment: "")
- case .uriNotCreated:
- return NSLocalizedString("Couldn't generate link due to missing connection URI", comment: "")
- case .failedToOpen:
- return NSLocalizedString("Given link couldn't be opened", comment: "")
+ wallets.forEach {
+ if
+ let walletScheme = $0.mobile.native,
+ !walletScheme.isEmpty,
+ schemes.contains(walletScheme.replacingOccurrences(of: "://", with: ""))
+ {
+ $0.installed = uiApplicationWrapper.canOpenURL(URL(string: walletScheme)!)
}
}
}
+
+ func loadRecentWallets() {
+ recentWalletStorage.recentWallets.forEach { wallet in
+ guard let lastTimeUsed = wallet.lastTimeUsed else { return }
+ setLastTimeUsed(wallet.id, date: lastTimeUsed)
+ }
+ }
+
+ func setLastTimeUsed(_ id: String, date: Date = Date()) {
+ wallets.first {
+ $0.id == id
+ }?.lastTimeUsed = date
+ recentWalletStorage.recentWallets = wallets
+ }
+}
+
+// MARK: - Deeplinking
+
+protocol WalletDeeplinkHandler {
+ func openAppstore(wallet: Listing)
+ func navigateToDeepLink(wallet: Listing, preferUniversal: Bool, preferBrowser: Bool)
+}
- func navigateToDeepLink(universalLink: String, nativeLink: String, preferUniversal: Bool) {
+extension ModalViewModel: WalletDeeplinkHandler {
+ func openAppstore(wallet: Listing) {
+ guard
+ let storeLinkString = wallet.app.ios,
+ let storeLink = URL(string: storeLinkString)
+ else { return }
+
+ uiApplicationWrapper.openURL(storeLink, nil)
+ }
+
+ func navigateToDeepLink(wallet: Listing, preferUniversal: Bool, preferBrowser: Bool) {
do {
- let nativeUrlString = try formatNativeUrlString(nativeLink)
- let universalUrlString = try formatUniversalUrlString(universalLink)
+ let nativeScheme = preferBrowser ? nil : wallet.mobile.native
+ let universalScheme = preferBrowser ? wallet.desktop.universal : wallet.mobile.universal
+
+ let nativeUrlString = try formatNativeUrlString(nativeScheme)
+ let universalUrlString = try formatUniversalUrlString(universalScheme)
if let nativeUrl = nativeUrlString?.toURL(), !preferUniversal {
uiApplicationWrapper.openURL(nativeUrl) { success in
@@ -291,7 +303,7 @@ private extension ModalViewModel {
if !success {
self.toast = Toast(style: .error, message: DeeplinkErrors.failedToOpen.localizedDescription)
}
- }
+ }
} else {
throw DeeplinkErrors.noWalletLinkFound
}
@@ -299,13 +311,32 @@ private extension ModalViewModel {
toast = Toast(style: .error, message: error.localizedDescription)
}
}
+}
+
+private extension ModalViewModel {
+ enum DeeplinkErrors: LocalizedError {
+ case noWalletLinkFound
+ case uriNotCreated
+ case failedToOpen
+
+ var errorDescription: String? {
+ switch self {
+ case .noWalletLinkFound:
+ return NSLocalizedString("No valid link for opening given wallet found", comment: "")
+ case .uriNotCreated:
+ return NSLocalizedString("Couldn't generate link due to missing connection URI", comment: "")
+ case .failedToOpen:
+ return NSLocalizedString("Given link couldn't be opened", comment: "")
+ }
+ }
+ }
func isHttpUrl(url: String) -> Bool {
return url.hasPrefix("http://") || url.hasPrefix("https://")
}
- func formatNativeUrlString(_ string: String) throws -> String? {
- if string.isEmpty { return nil }
+ func formatNativeUrlString(_ string: String?) throws -> String? {
+ guard let string = string, !string.isEmpty else { return nil }
if isHttpUrl(url: string) {
return try formatUniversalUrlString(string)
@@ -324,8 +355,8 @@ private extension ModalViewModel {
return "\(safeAppUrl)wc?uri=\(deeplinkUri)"
}
- func formatUniversalUrlString(_ string: String) throws -> String? {
- if string.isEmpty { return nil }
+ func formatUniversalUrlString(_ string: String?) throws -> String? {
+ guard let string = string, !string.isEmpty else { return nil }
if !isHttpUrl(url: string) {
return try formatNativeUrlString(string)
diff --git a/Sources/WalletConnectModal/Modal/RecentWalletStorage.swift b/Sources/WalletConnectModal/Modal/RecentWalletStorage.swift
index 04487edce..00ccd5929 100644
--- a/Sources/WalletConnectModal/Modal/RecentWalletStorage.swift
+++ b/Sources/WalletConnectModal/Modal/RecentWalletStorage.swift
@@ -9,23 +9,44 @@ final class RecentWalletsStorage {
var recentWallets: [Listing] {
get {
- guard
- let data = defaults.data(forKey: "recentWallets"),
- let wallets = try? JSONDecoder().decode([Listing].self, from: data)
- else {
- return []
- }
-
- return wallets
+ loadRecentWallets()
}
set {
- guard
- let walletsData = try? JSONEncoder().encode(newValue)
- else {
- return
+ saveRecentWallets(newValue)
+ }
+ }
+
+ func loadRecentWallets() -> [Listing] {
+ guard
+ let data = defaults.data(forKey: "recentWallets"),
+ let wallets = try? JSONDecoder().decode([Listing].self, from: data)
+ else {
+ return []
+ }
+
+ return wallets.filter { listing in
+ guard let lastTimeUsed = listing.lastTimeUsed else {
+ assertionFailure("Shouldn't happen we stored wallet without `lastTimeUsed`")
+ return false
}
- defaults.set(walletsData, forKey: "recentWallets")
+ // Consider Recent only for 3 days
+ return abs(lastTimeUsed.timeIntervalSinceNow) > (24 * 60 * 60 * 3)
+ }
+ }
+
+ func saveRecentWallets(_ listings: [Listing]) {
+
+ let subset = Array(listings.filter {
+ $0.lastTimeUsed != nil
+ }.prefix(5))
+
+ guard
+ let walletsData = try? JSONEncoder().encode(subset)
+ else {
+ return
}
+
+ defaults.set(walletsData, forKey: "recentWallets")
}
}
diff --git a/Sources/WalletConnectModal/Modal/Screens/WalletDetail.swift b/Sources/WalletConnectModal/Modal/Screens/WalletDetail/WalletDetail.swift
similarity index 50%
rename from Sources/WalletConnectModal/Modal/Screens/WalletDetail.swift
rename to Sources/WalletConnectModal/Modal/Screens/WalletDetail/WalletDetail.swift
index c8f56798c..3bae82d17 100644
--- a/Sources/WalletConnectModal/Modal/Screens/WalletDetail.swift
+++ b/Sources/WalletConnectModal/Modal/Screens/WalletDetail/WalletDetail.swift
@@ -3,29 +3,71 @@ import SwiftUI
struct WalletDetail: View {
@Environment(\.verticalSizeClass) var verticalSizeClass
- @State var wallet: Listing
- @State var retryShown: Bool = false
+ @ObservedObject var viewModel: WalletDetailViewModel
- let deeplink: (Listing) -> Void
- var deeplinkUniversal: (Listing) -> Void
- var openAppStore: (Listing) -> Void
+ @State var retryShown: Bool = false
var body: some View {
- content()
- .onAppear {
- if verticalSizeClass == .compact {
- retryShown = true
- } else {
- DispatchQueue.main.asyncAfter(deadline: .now() + 1.5) {
- withAnimation {
- retryShown = true
+ VStack {
+ if viewModel.showToggle {
+ Web3ModalPicker(
+ WalletDetailViewModel.Platform.allCases,
+ selection: viewModel.preferredPlatform
+ ) { item in
+
+ HStack {
+ switch item {
+ case .native:
+ Image(systemName: "iphone")
+ case .browser:
+ Image(systemName: "safari")
+ }
+ Text(item.rawValue.capitalized)
+ }
+ .font(.system(size: 14).weight(.semibold))
+ .multilineTextAlignment(.center)
+ .foregroundColor(viewModel.preferredPlatform == item ? .foreground1 : .foreground2)
+ .frame(maxWidth: .infinity)
+ .contentShape(Rectangle())
+ .padding(.horizontal, 8)
+ .padding(.vertical, 8)
+ .onTapGesture {
+ withAnimation(.easeInOut(duration: 0.15)) {
+ viewModel.preferredPlatform = item
}
}
}
+ .pickerBackgroundColor(.background2)
+ .cornerRadius(20)
+ .borderWidth(1)
+ .borderColor(.thinOverlay)
+ .accentColor(.thinOverlay)
+ .frame(maxWidth: 250)
+ .padding()
}
- .onDisappear {
- retryShown = false
- }
+
+ content()
+ .onAppear {
+
+ DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
+ viewModel.handle(.onAppear)
+ }
+
+ if verticalSizeClass == .compact {
+ retryShown = true
+ } else {
+ DispatchQueue.main.asyncAfter(deadline: .now() + 1.5) {
+ withAnimation {
+ retryShown = true
+ }
+ }
+ }
+ }
+ .onDisappear {
+ retryShown = false
+ }
+ .animation(.easeInOut, value: viewModel.preferredPlatform)
+ }
}
@ViewBuilder
@@ -41,9 +83,11 @@ struct WalletDetail: View {
retrySection()
}
- Divider()
-
- appStoreRow()
+ VStack {
+ Divider()
+ appStoreRow()
+ }
+ .opacity(viewModel.preferredPlatform != .native ? 0 : 1)
}
.padding(.horizontal, 20)
}
@@ -56,12 +100,15 @@ struct WalletDetail: View {
VStack(spacing: 15) {
if retryShown {
retrySection()
+ .frame(maxWidth: .infinity)
.padding(.top, 15)
}
- Divider()
-
- appStoreRow()
+ VStack {
+ Divider()
+ appStoreRow()
+ }
+ .opacity(viewModel.preferredPlatform != .native ? 0 : 1)
}
.padding(.horizontal, 20)
.padding(.bottom, 40)
@@ -72,7 +119,7 @@ struct WalletDetail: View {
func walletImage() -> some View {
VStack(spacing: 20) {
- WalletImage(wallet: wallet, size: .large)
+ WalletImage(wallet: viewModel.wallet, size: .large)
.frame(width: 96, height: 96)
.cornerRadius(24)
.overlay(
@@ -80,7 +127,7 @@ struct WalletDetail: View {
.stroke(.gray.opacity(0.4), lineWidth: 1)
)
- Text("Continue in \(wallet.name)...")
+ Text("Continue in \(viewModel.wallet.name)...")
.font(.system(size: 16, weight: .medium))
.foregroundColor(.foreground1)
}
@@ -88,10 +135,7 @@ struct WalletDetail: View {
func retrySection() -> some View {
VStack(spacing: 15) {
- let hasUniversalLink = wallet.mobile.universal?.isEmpty == false
- let hasNativeLink = wallet.mobile.native?.isEmpty == false
-
- Text("You can try opening \(wallet.name) again \((hasNativeLink && hasUniversalLink) ? "or try using a Universal Link instead" : "")")
+ Text("You can try opening \(viewModel.wallet.name) again \((viewModel.hasNativeLink && viewModel.showUniversalLink) ? "or try using a Universal Link instead" : "")")
.font(.system(size: 14, weight: .medium))
.multilineTextAlignment(.center)
.foregroundColor(.foreground2)
@@ -99,15 +143,15 @@ struct WalletDetail: View {
HStack {
Button {
- deeplink(wallet)
+ viewModel.handle(.didTapTryAgain)
} label: {
Text("Try Again")
}
.buttonStyle(WCMAccentButtonStyle())
- if hasUniversalLink {
+ if viewModel.showUniversalLink {
Button {
- deeplinkUniversal(wallet)
+ viewModel.handle(.didTapUniversalLink)
} label: {
Text("Universal link")
}
@@ -115,16 +159,17 @@ struct WalletDetail: View {
}
}
}
+ .frame(height: 100)
}
func appStoreRow() -> some View {
HStack(spacing: 0) {
HStack(spacing: 10) {
- WalletImage(wallet: wallet, size: .small)
+ WalletImage(wallet: viewModel.wallet, size: .small)
.frame(width: 28, height: 28)
.cornerRadius(8)
- Text("Get \(wallet.name)")
+ Text("Get \(viewModel.wallet.name)")
.font(.system(size: 16).weight(.semibold))
.foregroundColor(.foreground1)
}
@@ -141,7 +186,7 @@ struct WalletDetail: View {
}
}
.onTapGesture {
- openAppStore(wallet)
+ viewModel.handle(.didTapAppStore)
}
}
}
diff --git a/Sources/WalletConnectModal/Modal/Screens/WalletDetail/WalletDetailViewModel.swift b/Sources/WalletConnectModal/Modal/Screens/WalletDetail/WalletDetailViewModel.swift
new file mode 100644
index 000000000..4b146927c
--- /dev/null
+++ b/Sources/WalletConnectModal/Modal/Screens/WalletDetail/WalletDetailViewModel.swift
@@ -0,0 +1,63 @@
+import Foundation
+
+final class WalletDetailViewModel: ObservableObject {
+ enum Platform: String, CaseIterable, Identifiable {
+ case native
+ case browser
+
+ var id: Self { self }
+ }
+
+ enum Event {
+ case onAppear
+ case didTapUniversalLink
+ case didTapTryAgain
+ case didTapAppStore
+ }
+
+ let wallet: Listing
+ let deeplinkHandler: WalletDeeplinkHandler
+
+ @Published var preferredPlatform: Platform = .native
+
+ var showToggle: Bool { wallet.app.browser != nil && wallet.app.ios != nil }
+ var showUniversalLink: Bool { preferredPlatform == .native && wallet.mobile.universal?.isEmpty == false }
+ var hasNativeLink: Bool { wallet.mobile.native?.isEmpty == false }
+
+ init(
+ wallet: Listing,
+ deeplinkHandler: WalletDeeplinkHandler
+ ) {
+ self.wallet = wallet
+ self.deeplinkHandler = deeplinkHandler
+ preferredPlatform = wallet.app.ios != nil ? .native : .browser
+ }
+
+ func handle(_ event: Event) {
+ switch event {
+ case .onAppear:
+ deeplinkHandler.navigateToDeepLink(
+ wallet: wallet,
+ preferUniversal: true,
+ preferBrowser: preferredPlatform == .browser
+ )
+
+ case .didTapUniversalLink:
+ deeplinkHandler.navigateToDeepLink(
+ wallet: wallet,
+ preferUniversal: true,
+ preferBrowser: preferredPlatform == .browser
+ )
+
+ case .didTapTryAgain:
+ deeplinkHandler.navigateToDeepLink(
+ wallet: wallet,
+ preferUniversal: false,
+ preferBrowser: preferredPlatform == .browser
+ )
+
+ case .didTapAppStore:
+ deeplinkHandler.openAppstore(wallet: wallet)
+ }
+ }
+}
diff --git a/Sources/WalletConnectModal/Modal/Screens/WalletList.swift b/Sources/WalletConnectModal/Modal/Screens/WalletList.swift
index 8a76bcd83..7ea02d286 100644
--- a/Sources/WalletConnectModal/Modal/Screens/WalletList.swift
+++ b/Sources/WalletConnectModal/Modal/Screens/WalletList.swift
@@ -35,6 +35,7 @@ struct WalletList: View {
case .viewAll:
viewAll()
.frame(minHeight: 250)
+ .animation(nil)
default:
EmptyView()
}
@@ -173,8 +174,8 @@ struct WalletList: View {
.foregroundColor(.foreground1)
.multilineTextAlignment(.center)
- Text("RECENT")
- .opacity(wallet.lastTimeUsed != nil ? 1 : 0)
+ Text(wallet.lastTimeUsed != nil ? "RECENT" : "INSTALLED")
+ .opacity(wallet.lastTimeUsed != nil || wallet.installed ? 1 : 0)
.font(.system(size: 10))
.foregroundColor(.foreground3)
.padding(.horizontal, 12)
diff --git a/Sources/WalletConnectModal/Networking/Explorer/ListingsResponse.swift b/Sources/WalletConnectModal/Networking/Explorer/ListingsResponse.swift
index bc7c91619..0ddd4446c 100644
--- a/Sources/WalletConnectModal/Networking/Explorer/ListingsResponse.swift
+++ b/Sources/WalletConnectModal/Networking/Explorer/ListingsResponse.swift
@@ -4,15 +4,51 @@ struct ListingsResponse: Codable {
let listings: [String: Listing]
}
-struct Listing: Codable, Hashable, Identifiable {
+class Listing: Codable, Hashable, Identifiable {
+ init(
+ id: String,
+ name: String,
+ homepage: String,
+ order: Int? = nil,
+ imageId: String,
+ app: Listing.App,
+ mobile: Listing.Links,
+ desktop: Listing.Links,
+ lastTimeUsed: Date? = nil,
+ installed: Bool = false
+ ) {
+ self.id = id
+ self.name = name
+ self.homepage = homepage
+ self.order = order
+ self.imageId = imageId
+ self.app = app
+ self.mobile = mobile
+ self.desktop = desktop
+ self.lastTimeUsed = lastTimeUsed
+ self.installed = installed
+ }
+
+ func hash(into hasher: inout Hasher) {
+ hasher.combine(id)
+ hasher.combine(name)
+ }
+
+ static func == (lhs: Listing, rhs: Listing) -> Bool {
+ lhs.id == rhs.id && lhs.name == rhs.name
+ }
+
let id: String
let name: String
let homepage: String
let order: Int?
let imageId: String
let app: App
- let mobile: Mobile
- var lastTimeUsed: Date?
+ let mobile: Links
+ let desktop: Links
+
+ var lastTimeUsed: Date?
+ var installed: Bool = false
private enum CodingKeys: String, CodingKey {
case id
@@ -22,16 +58,16 @@ struct Listing: Codable, Hashable, Identifiable {
case imageId = "image_id"
case app
case mobile
+ case desktop
case lastTimeUsed
}
struct App: Codable, Hashable {
let ios: String?
- let mac: String?
- let safari: String?
+ let browser: String?
}
- struct Mobile: Codable, Hashable {
+ struct Links: Codable, Hashable {
let native: String?
let universal: String?
}
diff --git a/Sources/WalletConnectModal/Resources/Color.swift b/Sources/WalletConnectModal/Resources/Color.swift
index 00a4fe434..3a01b85f8 100644
--- a/Sources/WalletConnectModal/Resources/Color.swift
+++ b/Sources/WalletConnectModal/Resources/Color.swift
@@ -20,17 +20,17 @@ extension Color {
self.init(asset.rawValue, bundle: .module)
}
- static let foreground1 = Color(.foreground1)
- static let foreground2 = Color(.foreground2)
- static let foreground3 = Color(.foreground3)
- static let foregroundInverse = Color(.foregroundInverse)
- static let background1 = Color(.background1)
- static let background2 = Color(.background2)
- static let background3 = Color(.background3)
- static let negative = Color(.negative)
- static let thickOverlay = Color(.thickOverlay)
- static let thinOverlay = Color(.thinOverlay)
- static let accent = Color(.accent)
+ static let foreground1 = Color(AssetColor.foreground1)
+ static let foreground2 = Color(AssetColor.foreground2)
+ static let foreground3 = Color(AssetColor.foreground3)
+ static let foregroundInverse = Color(AssetColor.foregroundInverse)
+ static let background1 = Color(AssetColor.background1)
+ static let background2 = Color(AssetColor.background2)
+ static let background3 = Color(AssetColor.background3)
+ static let negative = Color(AssetColor.negative)
+ static let thickOverlay = Color(AssetColor.thickOverlay)
+ static let thinOverlay = Color(AssetColor.thinOverlay)
+ static var accent = Color(AssetColor.accent)
}
#if canImport(UIKit)
diff --git a/Sources/WalletConnectModal/UI/Common/Web3ModalPicker.swift b/Sources/WalletConnectModal/UI/Common/Web3ModalPicker.swift
new file mode 100644
index 000000000..0822b7fc9
--- /dev/null
+++ b/Sources/WalletConnectModal/UI/Common/Web3ModalPicker.swift
@@ -0,0 +1,127 @@
+import SwiftUI
+
+struct Web3ModalPicker: View where Data: Hashable, Content: View {
+ let sources: [Data]
+ let selection: Data?
+ let itemBuilder: (Data) -> Content
+
+ @State private var backgroundColor: Color = Color.black.opacity(0.05)
+
+ func pickerBackgroundColor(_ color: Color) -> Web3ModalPicker {
+ var view = self
+ view._backgroundColor = State(initialValue: color)
+ return view
+ }
+
+ @State private var cornerRadius: CGFloat?
+
+ func cornerRadius(_ cornerRadius: CGFloat) -> Web3ModalPicker {
+ var view = self
+ view._cornerRadius = State(initialValue: cornerRadius)
+ return view
+ }
+
+ @State private var borderColor: Color?
+
+ func borderColor(_ borderColor: Color) -> Web3ModalPicker {
+ var view = self
+ view._borderColor = State(initialValue: borderColor)
+ return view
+ }
+
+ @State private var borderWidth: CGFloat?
+
+ func borderWidth(_ borderWidth: CGFloat) -> Web3ModalPicker {
+ var view = self
+ view._borderWidth = State(initialValue: borderWidth)
+ return view
+ }
+
+ private var customIndicator: AnyView?
+
+ init(
+ _ sources: [Data],
+ selection: Data?,
+ @ViewBuilder itemBuilder: @escaping (Data) -> Content
+ ) {
+ self.sources = sources
+ self.selection = selection
+ self.itemBuilder = itemBuilder
+ }
+
+ public var body: some View {
+ ZStack(alignment: .center) {
+ if let selection = selection, let selectedIdx = sources.firstIndex(of: selection) {
+
+ GeometryReader { geo in
+ RoundedRectangle(cornerRadius: cornerRadius ?? 6.0)
+ .stroke(borderColor ?? .clear, lineWidth: borderWidth ?? 0)
+ .foregroundColor(.accentColor)
+ .padding(EdgeInsets(top: borderWidth ?? 2, leading: borderWidth ?? 2, bottom: borderWidth ?? 2, trailing: borderWidth ?? 2))
+ .frame(width: geo.size.width / CGFloat(sources.count))
+ .animation(.spring().speed(1.5), value: selection)
+ .offset(x: geo.size.width / CGFloat(sources.count) * CGFloat(selectedIdx), y: 0)
+ }.frame(height: 32)
+ }
+
+ HStack(spacing: 0) {
+ ForEach(sources, id: \.self) { item in
+ itemBuilder(item)
+ }
+ }
+ }
+ .background(
+ RoundedRectangle(cornerRadius: cornerRadius ?? 6.0)
+ .fill(backgroundColor)
+ .padding(-5)
+ )
+ }
+}
+
+struct PreviewWeb3ModalPicker: View {
+
+ enum Platform: String, CaseIterable {
+ case native
+ case browser
+ }
+
+ @State private var selectedItem: Platform? = .native
+
+ var body: some View {
+ Web3ModalPicker(
+ Platform.allCases,
+ selection: selectedItem
+ ) { item in
+
+ HStack {
+ Image(systemName: "iphone")
+ Text(item.rawValue.capitalized)
+ }
+ .font(.system(size: 14).weight(.semibold))
+ .multilineTextAlignment(.center)
+ .foregroundColor(selectedItem == item ? .foreground1 : .foreground2)
+ .frame(maxWidth: .infinity)
+ .contentShape(Rectangle())
+ .padding(.horizontal, 8)
+ .padding(.vertical, 8)
+ .onTapGesture {
+ withAnimation(.easeInOut(duration: 0.15)) {
+ selectedItem = item
+ }
+ }
+ }
+ .pickerBackgroundColor(.background2)
+ .cornerRadius(20)
+ .borderWidth(1)
+ .borderColor(.thinOverlay)
+ .accentColor(.thinOverlay)
+ .frame(maxWidth: 250)
+ .padding()
+ }
+}
+
+struct Web3ModalPicker_Previews: PreviewProvider {
+ static var previews: some View {
+ PreviewWeb3ModalPicker()
+ }
+}
diff --git a/Sources/WalletConnectModal/WalletConnectModal.swift b/Sources/WalletConnectModal/WalletConnectModal.swift
index d405d9178..7fe3c3674 100644
--- a/Sources/WalletConnectModal/WalletConnectModal.swift
+++ b/Sources/WalletConnectModal/WalletConnectModal.swift
@@ -1,4 +1,5 @@
import Foundation
+import SwiftUI
#if canImport(UIKit)
import UIKit
@@ -53,7 +54,8 @@ public class WalletConnectModal {
metadata: AppMetadata,
sessionParams: SessionParams = .default,
recommendedWalletIds: [String] = [],
- excludedWalletIds: [String] = []
+ excludedWalletIds: [String] = [],
+ accentColor: Color? = nil
) {
Pair.configure(metadata: metadata)
WalletConnectModal.config = WalletConnectModal.Config(
@@ -63,6 +65,10 @@ public class WalletConnectModal {
recommendedWalletIds: recommendedWalletIds,
excludedWalletIds: excludedWalletIds
)
+
+ if let accentColor {
+ Color.accent = accentColor
+ }
}
public static func set(sessionParams: SessionParams) {
diff --git a/Sources/WalletConnectNetworking/NetworkInteracting.swift b/Sources/WalletConnectNetworking/NetworkInteracting.swift
index ca15160a8..54987272c 100644
--- a/Sources/WalletConnectNetworking/NetworkInteracting.swift
+++ b/Sources/WalletConnectNetworking/NetworkInteracting.swift
@@ -3,6 +3,7 @@ import Combine
public protocol NetworkInteracting {
var socketConnectionStatusPublisher: AnyPublisher { get }
+ var networkConnectionStatusPublisher: AnyPublisher { get }
var requestPublisher: AnyPublisher<(topic: String, request: RPCRequest, decryptedPayload: Data, publishedAt: Date, derivedTopic: String?), Never> { get }
func subscribe(topic: String) async throws
func unsubscribe(topic: String)
@@ -12,7 +13,8 @@ public protocol NetworkInteracting {
func respond(topic: String, response: RPCResponse, protocolMethod: ProtocolMethod, envelopeType: Envelope.EnvelopeType) async throws
func respondSuccess(topic: String, requestId: RPCID, protocolMethod: ProtocolMethod, envelopeType: Envelope.EnvelopeType) async throws
func respondError(topic: String, requestId: RPCID, protocolMethod: ProtocolMethod, reason: Reason, envelopeType: Envelope.EnvelopeType) async throws
-
+ func handleHistoryRequest(topic: String, request: RPCRequest)
+
func requestSubscription(
on request: ProtocolMethod
) -> AnyPublisher, Never>
diff --git a/Sources/WalletConnectNetworking/NetworkingClient.swift b/Sources/WalletConnectNetworking/NetworkingClient.swift
index 150208766..e7c7fcd76 100644
--- a/Sources/WalletConnectNetworking/NetworkingClient.swift
+++ b/Sources/WalletConnectNetworking/NetworkingClient.swift
@@ -3,6 +3,8 @@ import Combine
public protocol NetworkingClient {
var socketConnectionStatusPublisher: AnyPublisher { get }
+ var logsPublisher: AnyPublisher {get}
+ func setLogging(level: LoggingLevel)
func connect() throws
func disconnect(closeCode: URLSessionWebSocketTask.CloseCode) throws
}
diff --git a/Sources/WalletConnectNetworking/NetworkingClientFactory.swift b/Sources/WalletConnectNetworking/NetworkingClientFactory.swift
index 6b5e39a2c..4470087d1 100644
--- a/Sources/WalletConnectNetworking/NetworkingClientFactory.swift
+++ b/Sources/WalletConnectNetworking/NetworkingClientFactory.swift
@@ -3,7 +3,7 @@ import Foundation
public struct NetworkingClientFactory {
public static func create(relayClient: RelayClient) -> NetworkingInteractor {
- let logger = ConsoleLogger(loggingLevel: .debug)
+ let logger = ConsoleLogger(prefix: "🕸️", loggingLevel: .off)
let keyValueStorage = UserDefaults.standard
let keychainStorage = KeychainStorage(serviceIdentifier: "com.walletconnect.sdk")
return NetworkingClientFactory.create(relayClient: relayClient, logger: logger, keychainStorage: keychainStorage, keyValueStorage: keyValueStorage)
@@ -12,7 +12,7 @@ public struct NetworkingClientFactory {
public static func create(relayClient: RelayClient, logger: ConsoleLogging, keychainStorage: KeychainStorageProtocol, keyValueStorage: KeyValueStorage) -> NetworkingInteractor {
let kms = KeyManagementService(keychain: keychainStorage)
- let serializer = Serializer(kms: kms)
+ let serializer = Serializer(kms: kms, logger: ConsoleLogger(prefix: "🔐", loggingLevel: .off))
let rpcHistory = RPCHistoryFactory.createForNetwork(keyValueStorage: keyValueStorage)
diff --git a/Sources/WalletConnectNetworking/NetworkingInteractor.swift b/Sources/WalletConnectNetworking/NetworkingInteractor.swift
index 931876d79..e92cfa3ea 100644
--- a/Sources/WalletConnectNetworking/NetworkingInteractor.swift
+++ b/Sources/WalletConnectNetworking/NetworkingInteractor.swift
@@ -1,7 +1,6 @@
import Foundation
import Combine
-
public class NetworkingInteractor: NetworkInteracting {
private var publishers = Set()
private let relayClient: RelayClient
@@ -20,7 +19,17 @@ public class NetworkingInteractor: NetworkInteracting {
responsePublisherSubject.eraseToAnyPublisher()
}
+ public var logsPublisher: AnyPublisher {
+ logger.logsPublisher
+ .merge(with: serializer.logsPublisher)
+ .merge(with: relayClient.logsPublisher)
+ .eraseToAnyPublisher()
+ }
+
+ public var networkConnectionStatusPublisher: AnyPublisher
public var socketConnectionStatusPublisher: AnyPublisher
+
+ private let networkMonitor: NetworkMonitoring
public init(
relayClient: RelayClient,
@@ -33,6 +42,8 @@ public class NetworkingInteractor: NetworkInteracting {
self.rpcHistory = rpcHistory
self.logger = logger
self.socketConnectionStatusPublisher = relayClient.socketConnectionStatusPublisher
+ self.networkMonitor = NetworkMonitor()
+ self.networkConnectionStatusPublisher = networkMonitor.networkConnectionStatusPublisher
setupRelaySubscribtion()
}
@@ -43,6 +54,13 @@ public class NetworkingInteractor: NetworkInteracting {
}.store(in: &publishers)
}
+ public func setLogging(level: LoggingLevel) {
+ logger.setLogging(level: level)
+ serializer.setLogging(level: level)
+ relayClient.setLogging(level: level)
+ }
+
+
public func subscribe(topic: String) async throws {
try await relayClient.subscribe(topic: topic)
}
@@ -144,6 +162,10 @@ public class NetworkingInteractor: NetworkInteracting {
logger.debug("Networking Interactor - Received unknown object type from networking relay")
}
}
+
+ public func handleHistoryRequest(topic: String, request: RPCRequest) {
+ requestPublisherSubject.send((topic, request, Data(), Date(), nil))
+ }
private func handleRequest(topic: String, request: RPCRequest, decryptedPayload: Data, publishedAt: Date, derivedTopic: String?) {
do {
diff --git a/Sources/WalletConnectNotify/Client/Common/DeleteNotifySubscriptionService.swift b/Sources/WalletConnectNotify/Client/Common/DeleteNotifySubscriptionService.swift
new file mode 100644
index 000000000..fc4a361fd
--- /dev/null
+++ b/Sources/WalletConnectNotify/Client/Common/DeleteNotifySubscriptionService.swift
@@ -0,0 +1,72 @@
+import Foundation
+
+class DeleteNotifySubscriptionService {
+ enum Errors: Error {
+ case notifySubscriptionNotFound
+ }
+ private let keyserver: URL
+ private let networkingInteractor: NetworkInteracting
+ private let identityClient: IdentityClient
+ private let webDidResolver: WebDidResolver
+ private let kms: KeyManagementServiceProtocol
+ private let logger: ConsoleLogging
+ private let notifyStorage: NotifyStorage
+
+ init(
+ keyserver: URL,
+ networkingInteractor: NetworkInteracting,
+ identityClient: IdentityClient,
+ webDidResolver: WebDidResolver,
+ kms: KeyManagementServiceProtocol,
+ logger: ConsoleLogging,
+ notifyStorage: NotifyStorage
+ ) {
+ self.keyserver = keyserver
+ self.networkingInteractor = networkingInteractor
+ self.identityClient = identityClient
+ self.webDidResolver = webDidResolver
+ self.kms = kms
+ self.logger = logger
+ self.notifyStorage = notifyStorage
+ }
+
+ func delete(topic: String) async throws {
+ logger.debug("Will delete notify subscription")
+
+ guard let subscription = notifyStorage.getSubscription(topic: topic)
+ else { throw Errors.notifySubscriptionNotFound}
+
+ let protocolMethod = NotifyDeleteProtocolMethod()
+ let dappPubKey = try await webDidResolver.resolvePublicKey(dappUrl: subscription.metadata.url)
+
+ let wrapper = try createJWTWrapper(
+ dappPubKey: DIDKey(rawData: dappPubKey.rawRepresentation),
+ reason: NotifyDeleteParams.userDisconnected.message,
+ app: subscription.metadata.url,
+ account: subscription.account
+ )
+
+ let request = RPCRequest(method: protocolMethod.method, params: wrapper)
+ try await networkingInteractor.request(request, topic: topic, protocolMethod: protocolMethod)
+
+ try notifyStorage.deleteSubscription(topic: topic)
+ notifyStorage.deleteMessages(topic: topic)
+
+ networkingInteractor.unsubscribe(topic: topic)
+
+ logger.debug("Subscription removed, topic: \(topic)")
+
+ kms.deleteSymmetricKey(for: topic)
+ }
+}
+
+private extension DeleteNotifySubscriptionService {
+
+ func createJWTWrapper(dappPubKey: DIDKey, reason: String, app: String, account: Account) throws -> NotifyDeletePayload.Wrapper {
+ let jwtPayload = NotifyDeletePayload(keyserver: keyserver, dappPubKey: dappPubKey, reason: reason, app: app)
+ return try identityClient.signAndCreateWrapper(
+ payload: jwtPayload,
+ account: account
+ )
+ }
+}
diff --git a/Sources/WalletConnectPush/Client/Common/DeletePushSubscriptionSubscriber.swift b/Sources/WalletConnectNotify/Client/Common/DeleteNotifySubscriptionSubscriber.swift
similarity index 60%
rename from Sources/WalletConnectPush/Client/Common/DeletePushSubscriptionSubscriber.swift
rename to Sources/WalletConnectNotify/Client/Common/DeleteNotifySubscriptionSubscriber.swift
index 7ec0f6f3a..435ece85f 100644
--- a/Sources/WalletConnectPush/Client/Common/DeletePushSubscriptionSubscriber.swift
+++ b/Sources/WalletConnectNotify/Client/Common/DeleteNotifySubscriptionSubscriber.swift
@@ -1,36 +1,34 @@
import Foundation
import Combine
-class DeletePushSubscriptionSubscriber {
+class DeleteNotifySubscriptionSubscriber {
private let networkingInteractor: NetworkInteracting
private let kms: KeyManagementServiceProtocol
private let logger: ConsoleLogging
private var publishers = [AnyCancellable]()
- private let pushStorage: PushStorage
+ private let notifyStorage: NotifyStorage
init(networkingInteractor: NetworkInteracting,
kms: KeyManagementServiceProtocol,
logger: ConsoleLogging,
- pushStorage: PushStorage
+ notifyStorage: NotifyStorage
) {
self.networkingInteractor = networkingInteractor
self.kms = kms
self.logger = logger
- self.pushStorage = pushStorage
+ self.notifyStorage = notifyStorage
subscribeForDeleteSubscription()
}
private func subscribeForDeleteSubscription() {
- let protocolMethod = PushDeleteProtocolMethod()
+ let protocolMethod = NotifyDeleteProtocolMethod()
networkingInteractor.requestSubscription(on: protocolMethod)
- .sink { [unowned self] (payload: RequestSubscriptionPayload) in
+ .sink { [unowned self] (payload: RequestSubscriptionPayload) in
+
+ guard let (_, _) = try? NotifyDeleteResponsePayload.decodeAndVerify(from: payload.request)
+ else { fatalError() /* TODO: Handle error */ }
+
logger.debug("Peer deleted subscription")
- let topic = payload.topic
- networkingInteractor.unsubscribe(topic: topic)
- Task(priority: .high) {
- try await pushStorage.deleteSubscription(topic: topic)
- }
- kms.deleteSymmetricKey(for: topic)
}.store(in: &publishers)
}
}
diff --git a/Sources/WalletConnectPush/Client/Common/PushDecryptionService.swift b/Sources/WalletConnectNotify/Client/Common/NotifyDecryptionService.swift
similarity index 59%
rename from Sources/WalletConnectPush/Client/Common/PushDecryptionService.swift
rename to Sources/WalletConnectNotify/Client/Common/NotifyDecryptionService.swift
index 995ff4e4a..5fe116af5 100644
--- a/Sources/WalletConnectPush/Client/Common/PushDecryptionService.swift
+++ b/Sources/WalletConnectNotify/Client/Common/NotifyDecryptionService.swift
@@ -1,8 +1,8 @@
import Foundation
-public class PushDecryptionService {
+public class NotifyDecryptionService {
enum Errors: Error {
- case malformedPushMessage
+ case malformedNotifyMessage
}
private let serializer: Serializing
@@ -13,12 +13,14 @@ public class PushDecryptionService {
public init() {
let keychainStorage = GroupKeychainStorage(serviceIdentifier: "group.com.walletconnect.sdk")
let kms = KeyManagementService(keychain: keychainStorage)
- self.serializer = Serializer(kms: kms)
+ self.serializer = Serializer(kms: kms, logger: ConsoleLogger(prefix: "🔐", loggingLevel: .off))
}
- public func decryptMessage(topic: String, ciphertext: String) throws -> PushMessage {
+ public func decryptMessage(topic: String, ciphertext: String) throws -> NotifyMessage {
let (rpcRequest, _, _): (RPCRequest, String?, Data) = try serializer.deserialize(topic: topic, encodedEnvelope: ciphertext)
- guard let params = rpcRequest.params else { throw Errors.malformedPushMessage }
- return try params.get(PushMessage.self)
+ guard let params = rpcRequest.params else { throw Errors.malformedNotifyMessage }
+ let wrapper = try params.get(NotifyMessagePayload.Wrapper.self)
+ let (messagePayload, _) = try NotifyMessagePayload.decodeAndVerify(from: wrapper)
+ return messagePayload.message
}
}
diff --git a/Sources/WalletConnectPush/Client/Common/PushResubscribeService.swift b/Sources/WalletConnectNotify/Client/Common/NotifyResubscribeService.swift
similarity index 54%
rename from Sources/WalletConnectPush/Client/Common/PushResubscribeService.swift
rename to Sources/WalletConnectNotify/Client/Common/NotifyResubscribeService.swift
index c19769f4d..80fbd12ec 100644
--- a/Sources/WalletConnectPush/Client/Common/PushResubscribeService.swift
+++ b/Sources/WalletConnectNotify/Client/Common/NotifyResubscribeService.swift
@@ -1,16 +1,18 @@
import Foundation
import Combine
-final class PushResubscribeService {
+final class NotifyResubscribeService {
private var publishers = Set()
+ private let logger: ConsoleLogging
private let networkInteractor: NetworkInteracting
- private let pushStorage: PushStorage
+ private let notifyStorage: NotifyStorage
- init(networkInteractor: NetworkInteracting, pushStorage: PushStorage) {
+ init(networkInteractor: NetworkInteracting, notifyStorage: NotifyStorage, logger: ConsoleLogging) {
self.networkInteractor = networkInteractor
- self.pushStorage = pushStorage
+ self.notifyStorage = notifyStorage
+ self.logger = logger
setUpResubscription()
}
@@ -18,7 +20,8 @@ final class PushResubscribeService {
networkInteractor.socketConnectionStatusPublisher
.sink { [unowned self] status in
guard status == .connected else { return }
- let topics = pushStorage.getSubscriptions().map{$0.topic}
+ let topics = notifyStorage.getSubscriptions().map{$0.topic}
+ logger.debug("Resubscribing to notify subscription topics: \(topics)", properties: ["topics": topics.joined(separator: ", ")])
Task(priority: .high) {
try await networkInteractor.batchSubscribe(topics: topics)
}
diff --git a/Sources/WalletConnectNotify/Client/Wallet/NotifyClient.swift b/Sources/WalletConnectNotify/Client/Wallet/NotifyClient.swift
new file mode 100644
index 000000000..8065dfa78
--- /dev/null
+++ b/Sources/WalletConnectNotify/Client/Wallet/NotifyClient.swift
@@ -0,0 +1,136 @@
+import Foundation
+import Combine
+
+public class NotifyClient {
+
+ private var publishers = Set()
+
+ /// publishes new subscriptions
+ public var newSubscriptionPublisher: AnyPublisher {
+ return notifyStorage.newSubscriptionPublisher
+ }
+
+ public var subscriptionErrorPublisher: AnyPublisher {
+ return notifySubscribeResponseSubscriber.subscriptionErrorPublisher
+ }
+
+ public var deleteSubscriptionPublisher: AnyPublisher {
+ return notifyStorage.deleteSubscriptionPublisher
+ }
+
+ public var subscriptionsPublisher: AnyPublisher<[NotifySubscription], Never> {
+ return notifyStorage.subscriptionsPublisher
+ }
+
+ public var notifyMessagePublisher: AnyPublisher {
+ return notifyMessageSubscriber.notifyMessagePublisher
+ }
+
+ public var updateSubscriptionPublisher: AnyPublisher {
+ return notifyStorage.updateSubscriptionPublisher
+ }
+
+ public var logsPublisher: AnyPublisher {
+ logger.logsPublisher
+ .eraseToAnyPublisher()
+ }
+
+ private let deleteNotifySubscriptionService: DeleteNotifySubscriptionService
+ private let notifySubscribeRequester: NotifySubscribeRequester
+
+ public let logger: ConsoleLogging
+
+ private let pushClient: PushClient
+ private let identityClient: IdentityClient
+ private let notifyStorage: NotifyStorage
+ private let notifyMessageSubscriber: NotifyMessageSubscriber
+ private let resubscribeService: NotifyResubscribeService
+ private let notifySubscribeResponseSubscriber: NotifySubscribeResponseSubscriber
+ private let deleteNotifySubscriptionSubscriber: DeleteNotifySubscriptionSubscriber
+ private let notifyUpdateRequester: NotifyUpdateRequester
+ private let notifyUpdateResponseSubscriber: NotifyUpdateResponseSubscriber
+ private let subscriptionsAutoUpdater: SubscriptionsAutoUpdater
+
+ init(logger: ConsoleLogging,
+ kms: KeyManagementServiceProtocol,
+ identityClient: IdentityClient,
+ pushClient: PushClient,
+ notifyMessageSubscriber: NotifyMessageSubscriber,
+ notifyStorage: NotifyStorage,
+ deleteNotifySubscriptionService: DeleteNotifySubscriptionService,
+ resubscribeService: NotifyResubscribeService,
+ notifySubscribeRequester: NotifySubscribeRequester,
+ notifySubscribeResponseSubscriber: NotifySubscribeResponseSubscriber,
+ deleteNotifySubscriptionSubscriber: DeleteNotifySubscriptionSubscriber,
+ notifyUpdateRequester: NotifyUpdateRequester,
+ notifyUpdateResponseSubscriber: NotifyUpdateResponseSubscriber,
+ subscriptionsAutoUpdater: SubscriptionsAutoUpdater
+ ) {
+ self.logger = logger
+ self.pushClient = pushClient
+ self.identityClient = identityClient
+ self.notifyMessageSubscriber = notifyMessageSubscriber
+ self.notifyStorage = notifyStorage
+ self.deleteNotifySubscriptionService = deleteNotifySubscriptionService
+ self.resubscribeService = resubscribeService
+ self.notifySubscribeRequester = notifySubscribeRequester
+ self.notifySubscribeResponseSubscriber = notifySubscribeResponseSubscriber
+ self.deleteNotifySubscriptionSubscriber = deleteNotifySubscriptionSubscriber
+ self.notifyUpdateRequester = notifyUpdateRequester
+ self.notifyUpdateResponseSubscriber = notifyUpdateResponseSubscriber
+ self.subscriptionsAutoUpdater = subscriptionsAutoUpdater
+ }
+
+ public func register(account: Account, onSign: @escaping SigningCallback) async throws {
+ _ = try await identityClient.register(account: account, onSign: onSign)
+ }
+
+ public func setLogging(level: LoggingLevel) {
+ logger.setLogging(level: level)
+ }
+
+ public func subscribe(metadata: AppMetadata, account: Account, onSign: @escaping SigningCallback) async throws {
+ try await notifySubscribeRequester.subscribe(metadata: metadata, account: account, onSign: onSign)
+ }
+
+ public func update(topic: String, scope: Set) async throws {
+ try await notifyUpdateRequester.update(topic: topic, scope: scope)
+ }
+
+ public func getActiveSubscriptions() -> [NotifySubscription] {
+ return notifyStorage.getSubscriptions()
+ }
+
+ public func getMessageHistory(topic: String) -> [NotifyMessageRecord] {
+ notifyStorage.getMessages(topic: topic)
+ }
+
+ public func deleteSubscription(topic: String) async throws {
+ try await deleteNotifySubscriptionService.delete(topic: topic)
+ }
+
+ public func deleteNotifyMessage(id: String) {
+ notifyStorage.deleteMessage(id: id)
+ }
+
+ public func register(deviceToken: Data) async throws {
+ try await pushClient.register(deviceToken: deviceToken)
+ }
+
+ public func isIdentityRegistered(account: Account) -> Bool {
+ return identityClient.isIdentityRegistered(account: account)
+ }
+
+ public func messagesPublisher(topic: String) -> AnyPublisher<[NotifyMessageRecord], Never> {
+ return notifyStorage.messagesPublisher(topic: topic)
+ }
+}
+
+#if targetEnvironment(simulator)
+extension NotifyClient {
+ public func register(deviceToken: String) async throws {
+ try await pushClient.register(deviceToken: deviceToken)
+ }
+}
+#endif
+
diff --git a/Sources/WalletConnectNotify/Client/Wallet/NotifyClientFactory.swift b/Sources/WalletConnectNotify/Client/Wallet/NotifyClientFactory.swift
new file mode 100644
index 000000000..cb77bce9a
--- /dev/null
+++ b/Sources/WalletConnectNotify/Client/Wallet/NotifyClientFactory.swift
@@ -0,0 +1,78 @@
+import Foundation
+
+public struct NotifyClientFactory {
+
+ public static func create(networkInteractor: NetworkInteracting, pairingRegisterer: PairingRegisterer, pushClient: PushClient, crypto: CryptoProvider) -> NotifyClient {
+ let logger = ConsoleLogger(prefix: "🔔",loggingLevel: .debug)
+ let keyValueStorage = UserDefaults.standard
+ let keyserverURL = URL(string: "https://keys.walletconnect.com")!
+ let keychainStorage = KeychainStorage(serviceIdentifier: "com.walletconnect.sdk")
+ let groupKeychainService = GroupKeychainStorage(serviceIdentifier: "group.com.walletconnect.sdk")
+
+ return NotifyClientFactory.create(
+ keyserverURL: keyserverURL,
+ logger: logger,
+ keyValueStorage: keyValueStorage,
+ keychainStorage: keychainStorage,
+ groupKeychainStorage: groupKeychainService,
+ networkInteractor: networkInteractor,
+ pairingRegisterer: pairingRegisterer,
+ pushClient: pushClient,
+ crypto: crypto
+ )
+ }
+
+ static func create(
+ keyserverURL: URL,
+ logger: ConsoleLogging,
+ keyValueStorage: KeyValueStorage,
+ keychainStorage: KeychainStorageProtocol,
+ groupKeychainStorage: KeychainStorageProtocol,
+ networkInteractor: NetworkInteracting,
+ pairingRegisterer: PairingRegisterer,
+ pushClient: PushClient,
+ crypto: CryptoProvider
+ ) -> NotifyClient {
+ let kms = KeyManagementService(keychain: keychainStorage)
+ let subscriptionStore = KeyedDatabase(storage: keyValueStorage, identifier: NotifyStorageIdntifiers.notifySubscription)
+ let messagesStore = KeyedDatabase(storage: keyValueStorage, identifier: NotifyStorageIdntifiers.notifyMessagesRecords)
+ let notifyStorage = NotifyStorage(subscriptionStore: subscriptionStore, messagesStore: messagesStore)
+ let identityClient = IdentityClientFactory.create(keyserver: keyserverURL, keychain: keychainStorage, logger: logger)
+ let notifyMessageSubscriber = NotifyMessageSubscriber(keyserver: keyserverURL, networkingInteractor: networkInteractor, identityClient: identityClient, notifyStorage: notifyStorage, crypto: crypto, logger: logger)
+ let webDidResolver = WebDidResolver()
+ let deleteNotifySubscriptionService = DeleteNotifySubscriptionService(keyserver: keyserverURL, networkingInteractor: networkInteractor, identityClient: identityClient, webDidResolver: webDidResolver, kms: kms, logger: logger, notifyStorage: notifyStorage)
+ let resubscribeService = NotifyResubscribeService(networkInteractor: networkInteractor, notifyStorage: notifyStorage, logger: logger)
+
+ let dappsMetadataStore = CodableStore(defaults: keyValueStorage, identifier: NotifyStorageIdntifiers.dappsMetadataStore)
+ let subscriptionScopeProvider = SubscriptionScopeProvider()
+
+ let notifySubscribeRequester = NotifySubscribeRequester(keyserverURL: keyserverURL, networkingInteractor: networkInteractor, identityClient: identityClient, logger: logger, kms: kms, webDidResolver: webDidResolver, subscriptionScopeProvider: subscriptionScopeProvider, dappsMetadataStore: dappsMetadataStore)
+
+ let notifySubscribeResponseSubscriber = NotifySubscribeResponseSubscriber(networkingInteractor: networkInteractor, kms: kms, logger: logger, groupKeychainStorage: groupKeychainStorage, notifyStorage: notifyStorage, dappsMetadataStore: dappsMetadataStore, subscriptionScopeProvider: subscriptionScopeProvider)
+
+ let notifyUpdateRequester = NotifyUpdateRequester(keyserverURL: keyserverURL, webDidResolver: webDidResolver, identityClient: identityClient, networkingInteractor: networkInteractor, subscriptionScopeProvider: subscriptionScopeProvider, logger: logger, notifyStorage: notifyStorage)
+
+ let notifyUpdateResponseSubscriber = NotifyUpdateResponseSubscriber(networkingInteractor: networkInteractor, logger: logger, subscriptionScopeProvider: subscriptionScopeProvider, notifyStorage: notifyStorage)
+
+ let deleteNotifySubscriptionSubscriber = DeleteNotifySubscriptionSubscriber(networkingInteractor: networkInteractor, kms: kms, logger: logger, notifyStorage: notifyStorage)
+
+ let subscriptionsAutoUpdater = SubscriptionsAutoUpdater(notifyUpdateRequester: notifyUpdateRequester, logger: logger, notifyStorage: notifyStorage)
+
+ return NotifyClient(
+ logger: logger,
+ kms: kms,
+ identityClient: identityClient,
+ pushClient: pushClient,
+ notifyMessageSubscriber: notifyMessageSubscriber,
+ notifyStorage: notifyStorage,
+ deleteNotifySubscriptionService: deleteNotifySubscriptionService,
+ resubscribeService: resubscribeService,
+ notifySubscribeRequester: notifySubscribeRequester,
+ notifySubscribeResponseSubscriber: notifySubscribeResponseSubscriber,
+ deleteNotifySubscriptionSubscriber: deleteNotifySubscriptionSubscriber,
+ notifyUpdateRequester: notifyUpdateRequester,
+ notifyUpdateResponseSubscriber: notifyUpdateResponseSubscriber,
+ subscriptionsAutoUpdater: subscriptionsAutoUpdater
+ )
+ }
+}
diff --git a/Sources/WalletConnectPush/Client/Wallet/PushMessageRecord.swift b/Sources/WalletConnectNotify/Client/Wallet/NotifyMessageRecord.swift
similarity index 60%
rename from Sources/WalletConnectPush/Client/Wallet/PushMessageRecord.swift
rename to Sources/WalletConnectNotify/Client/Wallet/NotifyMessageRecord.swift
index 214d9234e..a9431587a 100644
--- a/Sources/WalletConnectPush/Client/Wallet/PushMessageRecord.swift
+++ b/Sources/WalletConnectNotify/Client/Wallet/NotifyMessageRecord.swift
@@ -1,9 +1,9 @@
import Foundation
-public struct PushMessageRecord: Codable, Equatable, DatabaseObject {
+public struct NotifyMessageRecord: Codable, Equatable, DatabaseObject {
public let id: String
public let topic: String
- public let message: PushMessage
+ public let message: NotifyMessage
public let publishedAt: Date
public var databaseId: String {
diff --git a/Sources/WalletConnectNotify/Client/Wallet/NotifyStorage.swift b/Sources/WalletConnectNotify/Client/Wallet/NotifyStorage.swift
new file mode 100644
index 000000000..ebc55c784
--- /dev/null
+++ b/Sources/WalletConnectNotify/Client/Wallet/NotifyStorage.swift
@@ -0,0 +1,123 @@
+import Foundation
+import Combine
+
+protocol NotifyStoring {
+ func getSubscriptions() -> [NotifySubscription]
+ func getSubscription(topic: String) -> NotifySubscription?
+ func setSubscription(_ subscription: NotifySubscription) async throws
+ func deleteSubscription(topic: String) async throws
+}
+
+final class NotifyStorage: NotifyStoring {
+
+ private var publishers = Set()
+
+ private let subscriptionStore: KeyedDatabase
+ private let messagesStore: KeyedDatabase
+
+ private let newSubscriptionSubject = PassthroughSubject()
+ private let updateSubscriptionSubject = PassthroughSubject()
+ private let deleteSubscriptionSubject = PassthroughSubject()
+ private let subscriptionsSubject = PassthroughSubject<[NotifySubscription], Never>()
+ private let messagesSubject = PassthroughSubject<[NotifyMessageRecord], Never>()
+
+ var newSubscriptionPublisher: AnyPublisher {
+ return newSubscriptionSubject.eraseToAnyPublisher()
+ }
+
+ var updateSubscriptionPublisher: AnyPublisher {
+ return updateSubscriptionSubject.eraseToAnyPublisher()
+ }
+
+ var deleteSubscriptionPublisher: AnyPublisher {
+ return deleteSubscriptionSubject.eraseToAnyPublisher()
+ }
+
+ var subscriptionsPublisher: AnyPublisher<[NotifySubscription], Never> {
+ return subscriptionsSubject.eraseToAnyPublisher()
+ }
+
+ var messagesPublisher: AnyPublisher<[NotifyMessageRecord], Never> {
+ return messagesSubject.eraseToAnyPublisher()
+ }
+
+ init(subscriptionStore: KeyedDatabase, messagesStore: KeyedDatabase) {
+ self.subscriptionStore = subscriptionStore
+ self.messagesStore = messagesStore
+
+ setupSubscriptions()
+ }
+
+ // MARK: Subscriptions
+
+ func getSubscriptions() -> [NotifySubscription] {
+ return subscriptionStore.getAll()
+ }
+
+ func getSubscription(topic: String) -> NotifySubscription? {
+ return subscriptionStore.getAll().first(where: { $0.topic == topic })
+ }
+
+ func setSubscription(_ subscription: NotifySubscription) {
+ subscriptionStore.set(element: subscription, for: subscription.account.absoluteString)
+ newSubscriptionSubject.send(subscription)
+ }
+
+ func deleteSubscription(topic: String) throws {
+ guard let subscription = getSubscription(topic: topic) else {
+ throw Errors.subscriptionNotFound
+ }
+ subscriptionStore.delete(id: topic, for: subscription.account.absoluteString)
+ deleteSubscriptionSubject.send(topic)
+ }
+
+ func updateSubscription(_ subscription: NotifySubscription, scope: [String: ScopeValue], expiry: UInt64) {
+ let expiry = Date(timeIntervalSince1970: TimeInterval(expiry))
+ let updated = NotifySubscription(topic: subscription.topic, account: subscription.account, relay: subscription.relay, metadata: subscription.metadata, scope: scope, expiry: expiry, symKey: subscription.symKey)
+ subscriptionStore.set(element: updated, for: updated.account.absoluteString)
+ updateSubscriptionSubject.send(updated)
+ }
+
+ // MARK: Messages
+
+ func messagesPublisher(topic: String) -> AnyPublisher<[NotifyMessageRecord], Never> {
+ return messagesPublisher
+ .map { $0.filter { $0.topic == topic } }
+ .eraseToAnyPublisher()
+ }
+
+ func getMessages(topic: String) -> [NotifyMessageRecord] {
+ return messagesStore.getAll(for: topic)
+ .sorted{$0.publishedAt > $1.publishedAt}
+ }
+
+ func deleteMessages(topic: String) {
+ messagesStore.deleteAll(for: topic)
+ }
+
+ func deleteMessage(id: String) {
+ guard let result = messagesStore.find(id: id) else { return }
+ messagesStore.delete(id: id, for: result.key)
+ }
+
+ func setMessage(_ record: NotifyMessageRecord) {
+ messagesStore.set(element: record, for: record.topic)
+ }
+}
+
+private extension NotifyStorage {
+
+ enum Errors: Error {
+ case subscriptionNotFound
+ }
+
+ func setupSubscriptions() {
+ messagesStore.onUpdate = { [unowned self] in
+ messagesSubject.send(messagesStore.getAll())
+ }
+
+ subscriptionStore.onUpdate = { [unowned self] in
+ subscriptionsSubject.send(subscriptionStore.getAll())
+ }
+ }
+}
diff --git a/Sources/WalletConnectNotify/Client/Wallet/ProtocolEngine/wc_notifyMessage/NotifyMessageSubscriber.swift b/Sources/WalletConnectNotify/Client/Wallet/ProtocolEngine/wc_notifyMessage/NotifyMessageSubscriber.swift
new file mode 100644
index 000000000..957c4df60
--- /dev/null
+++ b/Sources/WalletConnectNotify/Client/Wallet/ProtocolEngine/wc_notifyMessage/NotifyMessageSubscriber.swift
@@ -0,0 +1,70 @@
+import Foundation
+import Combine
+
+class NotifyMessageSubscriber {
+ private let keyserver: URL
+ private let networkingInteractor: NetworkInteracting
+ private let identityClient: IdentityClient
+ private let notifyStorage: NotifyStorage
+ private let crypto: CryptoProvider
+ private let logger: ConsoleLogging
+ private var publishers = [AnyCancellable]()
+ private let notifyMessagePublisherSubject = PassthroughSubject()
+
+ public var notifyMessagePublisher: AnyPublisher {
+ notifyMessagePublisherSubject.eraseToAnyPublisher()
+ }
+
+ init(keyserver: URL, networkingInteractor: NetworkInteracting, identityClient: IdentityClient, notifyStorage: NotifyStorage, crypto: CryptoProvider, logger: ConsoleLogging) {
+ self.keyserver = keyserver
+ self.networkingInteractor = networkingInteractor
+ self.identityClient = identityClient
+ self.notifyStorage = notifyStorage
+ self.crypto = crypto
+ self.logger = logger
+ subscribeForNotifyMessages()
+ }
+
+ private func subscribeForNotifyMessages() {
+ let protocolMethod = NotifyMessageProtocolMethod()
+ networkingInteractor.requestSubscription(on: protocolMethod)
+ .sink { [unowned self] (payload: RequestSubscriptionPayload) in
+
+ logger.debug("Received Notify Message on topic: \(payload.topic)", properties: ["topic": payload.topic])
+
+ Task(priority: .high) {
+ let (messagePayload, claims) = try NotifyMessagePayload.decodeAndVerify(from: payload.request)
+ logger.debug("Decoded Notify Message: \(payload.topic)", properties: ["topic": payload.topic, "messageBody": messagePayload.message.body, "messageTitle": messagePayload.message.title, "publishedAt": payload.publishedAt.description, "id": payload.id.string])
+ let dappPubKey = try DIDKey(did: claims.iss)
+ let messageData = try JSONEncoder().encode(messagePayload.message)
+
+ let record = NotifyMessageRecord(id: payload.id.string, topic: payload.topic, message: messagePayload.message, publishedAt: payload.publishedAt)
+ notifyStorage.setMessage(record)
+ notifyMessagePublisherSubject.send(record)
+
+ let receiptPayload = NotifyMessageReceiptPayload(
+ keyserver: keyserver, dappPubKey: dappPubKey,
+ messageHash: crypto.keccak256(messageData).toHexString(),
+ app: messagePayload.app
+ )
+
+ let wrapper = try identityClient.signAndCreateWrapper(
+ payload: receiptPayload,
+ account: messagePayload.account
+ )
+
+ let response = RPCResponse(id: payload.id, result: wrapper)
+
+ try await networkingInteractor.respond(
+ topic: payload.topic,
+ response: response,
+ protocolMethod: NotifyMessageProtocolMethod()
+ )
+
+ logger.debug("Sent Notify Message Response on topic: \(payload.topic)", properties: ["topic" : payload.topic, "messageBody": messagePayload.message.body, "messageTitle": messagePayload.message.title, "id": payload.id.string, "result": wrapper.jwtString])
+ }
+
+ }.store(in: &publishers)
+
+ }
+}
diff --git a/Sources/WalletConnectNotify/Client/Wallet/ProtocolEngine/wc_notifyUpdate/NotifyUpdateRequester.swift b/Sources/WalletConnectNotify/Client/Wallet/ProtocolEngine/wc_notifyUpdate/NotifyUpdateRequester.swift
new file mode 100644
index 000000000..8f4c8a103
--- /dev/null
+++ b/Sources/WalletConnectNotify/Client/Wallet/ProtocolEngine/wc_notifyUpdate/NotifyUpdateRequester.swift
@@ -0,0 +1,66 @@
+import Foundation
+
+protocol NotifyUpdateRequesting {
+ func update(topic: String, scope: Set) async throws
+}
+
+class NotifyUpdateRequester: NotifyUpdateRequesting {
+ enum Errors: Error {
+ case noSubscriptionForGivenTopic
+ }
+
+ private let keyserverURL: URL
+ private let webDidResolver: WebDidResolver
+ private let identityClient: IdentityClient
+ private let networkingInteractor: NetworkInteracting
+ private let subscriptionScopeProvider: SubscriptionScopeProvider
+ private let logger: ConsoleLogging
+ private let notifyStorage: NotifyStorage
+
+ init(
+ keyserverURL: URL,
+ webDidResolver: WebDidResolver,
+ identityClient: IdentityClient,
+ networkingInteractor: NetworkInteracting,
+ subscriptionScopeProvider: SubscriptionScopeProvider,
+ logger: ConsoleLogging,
+ notifyStorage: NotifyStorage
+ ) {
+ self.keyserverURL = keyserverURL
+ self.webDidResolver = webDidResolver
+ self.identityClient = identityClient
+ self.networkingInteractor = networkingInteractor
+ self.subscriptionScopeProvider = subscriptionScopeProvider
+ self.logger = logger
+ self.notifyStorage = notifyStorage
+ }
+
+ func update(topic: String, scope: Set) async throws {
+ logger.debug("NotifyUpdateRequester: updating subscription for topic: \(topic)")
+
+ guard let subscription = notifyStorage.getSubscription(topic: topic) else { throw Errors.noSubscriptionForGivenTopic }
+
+ let dappPubKey = try await webDidResolver.resolvePublicKey(dappUrl: subscription.metadata.url)
+
+ let request = try createJWTRequest(
+ dappPubKey: DIDKey(rawData: dappPubKey.rawRepresentation),
+ subscriptionAccount: subscription.account,
+ dappUrl: subscription.metadata.url, scope: scope
+ )
+
+ let protocolMethod = NotifyUpdateProtocolMethod()
+
+ try await networkingInteractor.request(request, topic: topic, protocolMethod: protocolMethod)
+ }
+
+ private func createJWTRequest(dappPubKey: DIDKey, subscriptionAccount: Account, dappUrl: String, scope: Set) throws -> RPCRequest {
+ let protocolMethod = NotifyUpdateProtocolMethod().method
+ let scopeClaim = scope.joined(separator: " ")
+ let jwtPayload = NotifyUpdatePayload(dappPubKey: dappPubKey, keyserver: keyserverURL, subscriptionAccount: subscriptionAccount, dappUrl: dappUrl, scope: scopeClaim)
+ let wrapper = try identityClient.signAndCreateWrapper(
+ payload: jwtPayload,
+ account: subscriptionAccount
+ )
+ return RPCRequest(method: protocolMethod, params: wrapper)
+ }
+}
diff --git a/Sources/WalletConnectNotify/Client/Wallet/ProtocolEngine/wc_notifyUpdate/NotifyUpdateResponseSubscriber.swift b/Sources/WalletConnectNotify/Client/Wallet/ProtocolEngine/wc_notifyUpdate/NotifyUpdateResponseSubscriber.swift
new file mode 100644
index 000000000..ce74dea16
--- /dev/null
+++ b/Sources/WalletConnectNotify/Client/Wallet/ProtocolEngine/wc_notifyUpdate/NotifyUpdateResponseSubscriber.swift
@@ -0,0 +1,65 @@
+import Foundation
+import Combine
+
+class NotifyUpdateResponseSubscriber {
+ private let networkingInteractor: NetworkInteracting
+ private var publishers = [AnyCancellable]()
+ private let logger: ConsoleLogging
+ private let notifyStorage: NotifyStorage
+ private let subscriptionScopeProvider: SubscriptionScopeProvider
+
+ init(networkingInteractor: NetworkInteracting,
+ logger: ConsoleLogging,
+ subscriptionScopeProvider: SubscriptionScopeProvider,
+ notifyStorage: NotifyStorage
+ ) {
+ self.networkingInteractor = networkingInteractor
+ self.logger = logger
+ self.notifyStorage = notifyStorage
+ self.subscriptionScopeProvider = subscriptionScopeProvider
+ subscribeForUpdateResponse()
+ }
+
+ // TODO: handle error response
+}
+
+private extension NotifyUpdateResponseSubscriber {
+ enum Errors: Error {
+ case subscriptionDoesNotExist
+ case selectedScopeNotFound
+ }
+
+ func subscribeForUpdateResponse() {
+ let protocolMethod = NotifyUpdateProtocolMethod()
+ networkingInteractor.responseSubscription(on: protocolMethod)
+ .sink {[unowned self] (payload: ResponseSubscriptionPayload) in
+ Task(priority: .high) {
+ logger.debug("Received Notify Update response")
+
+ let subscriptionTopic = payload.topic
+
+ let (requestPayload, requestClaims) = try NotifyUpdatePayload.decodeAndVerify(from: payload.request)
+ let (_, _) = try NotifyUpdateResponsePayload.decodeAndVerify(from: payload.response)
+
+ let scope = try await buildScope(selected: requestPayload.scope, dappUrl: requestPayload.dappUrl)
+
+ guard let oldSubscription = notifyStorage.getSubscription(topic: subscriptionTopic) else {
+ logger.debug("NotifyUpdateResponseSubscriber Subscription does not exist")
+ return
+ }
+
+ notifyStorage.updateSubscription(oldSubscription, scope: scope, expiry: requestClaims.exp)
+
+ logger.debug("Updated Subscription")
+ }
+ }.store(in: &publishers)
+ }
+
+ func buildScope(selected: String, dappUrl: String) async throws -> [String: ScopeValue] {
+ let selectedScope = selected.components(separatedBy: " ")
+ let availableScope = try await subscriptionScopeProvider.getSubscriptionScope(dappUrl: dappUrl)
+ return availableScope.reduce(into: [:]) {
+ $0[$1.name] = ScopeValue(description: $1.description, enabled: selectedScope.contains($1.name))
+ }
+ }
+}
diff --git a/Sources/WalletConnectPush/Client/Wallet/ProtocolEngine/wc_pushSubscribe/PushSubscribeRequester.swift b/Sources/WalletConnectNotify/Client/Wallet/ProtocolEngine/wc_pushSubscribe/NotifySubscribeRequester.swift
similarity index 61%
rename from Sources/WalletConnectPush/Client/Wallet/ProtocolEngine/wc_pushSubscribe/PushSubscribeRequester.swift
rename to Sources/WalletConnectNotify/Client/Wallet/ProtocolEngine/wc_pushSubscribe/NotifySubscribeRequester.swift
index cfd061084..7a77693ed 100644
--- a/Sources/WalletConnectPush/Client/Wallet/ProtocolEngine/wc_pushSubscribe/PushSubscribeRequester.swift
+++ b/Sources/WalletConnectNotify/Client/Wallet/ProtocolEngine/wc_pushSubscribe/NotifySubscribeRequester.swift
@@ -1,12 +1,9 @@
import Foundation
-class PushSubscribeRequester {
+class NotifySubscribeRequester {
enum Errors: Error {
- case didDocDoesNotContainKeyAgreement
- case noVerificationMethodForKey
- case unsupportedCurve
case signatureRejected
}
@@ -24,7 +21,7 @@ class PushSubscribeRequester {
identityClient: IdentityClient,
logger: ConsoleLogging,
kms: KeyManagementService,
- webDidResolver: WebDidResolver = WebDidResolver(),
+ webDidResolver: WebDidResolver,
subscriptionScopeProvider: SubscriptionScopeProvider,
dappsMetadataStore: CodableStore
) {
@@ -38,35 +35,36 @@ class PushSubscribeRequester {
self.dappsMetadataStore = dappsMetadataStore
}
- @discardableResult func subscribe(metadata: AppMetadata, account: Account, onSign: @escaping SigningCallback) async throws -> SubscriptionJWTPayload.Wrapper {
+ @discardableResult func subscribe(metadata: AppMetadata, account: Account, onSign: @escaping SigningCallback) async throws -> NotifySubscriptionPayload.Wrapper {
let dappUrl = metadata.url
- logger.debug("Subscribing for Push")
+ logger.debug("Subscribing for Notify")
- let peerPublicKey = try await resolvePublicKey(dappUrl: metadata.url)
+ let peerPublicKey = try await webDidResolver.resolvePublicKey(dappUrl: metadata.url)
let subscribeTopic = peerPublicKey.rawRepresentation.sha256().toHexString()
let keysY = try generateAgreementKeys(peerPublicKey: peerPublicKey)
- let responseTopic = keysY.derivedTopic()
+ let responseTopic = keysY.derivedTopic()
dappsMetadataStore.set(metadata, forKey: responseTopic)
try kms.setSymmetricKey(keysY.sharedKey, for: subscribeTopic)
-
- _ = try await identityClient.register(account: account, onSign: onSign)
-
try kms.setAgreementSecret(keysY, topic: responseTopic)
logger.debug("setting symm key for response topic \(responseTopic)")
- let protocolMethod = PushSubscribeProtocolMethod()
+ let protocolMethod = NotifySubscribeProtocolMethod()
- let subscriptionAuthWrapper = try await createJWTWrapper(subscriptionAccount: account, dappUrl: dappUrl)
+ let subscriptionAuthWrapper = try await createJWTWrapper(
+ dappPubKey: DIDKey(did: peerPublicKey.did),
+ subscriptionAccount: account,
+ dappUrl: dappUrl
+ )
let request = RPCRequest(method: protocolMethod.method, params: subscriptionAuthWrapper)
- logger.debug("PushSubscribeRequester: subscribing to response topic: \(responseTopic)")
+ logger.debug("NotifySubscribeRequester: subscribing to response topic: \(responseTopic)")
try await networkingInteractor.subscribe(topic: responseTopic)
@@ -74,17 +72,6 @@ class PushSubscribeRequester {
return subscriptionAuthWrapper
}
- private func resolvePublicKey(dappUrl: String) async throws -> AgreementPublicKey {
- logger.debug("PushSubscribeRequester: Resolving DIDDoc for: \(dappUrl)")
- let didDoc = try await webDidResolver.resolveDidDoc(domainUrl: dappUrl)
- guard let keyAgreement = didDoc.keyAgreement.first else { throw Errors.didDocDoesNotContainKeyAgreement }
- guard let verificationMethod = didDoc.verificationMethod.first(where: { verificationMethod in verificationMethod.id == keyAgreement }) else { throw Errors.noVerificationMethodForKey }
- guard verificationMethod.publicKeyJwk.crv == .X25519 else { throw Errors.unsupportedCurve}
- let pubKeyBase64Url = verificationMethod.publicKeyJwk.x
- return try AgreementPublicKey(base64url: pubKeyBase64Url)
- }
-
-
private func generateAgreementKeys(peerPublicKey: AgreementPublicKey) throws -> AgreementKeys {
let selfPubKey = try kms.createX25519KeyPair()
@@ -92,10 +79,10 @@ class PushSubscribeRequester {
return keys
}
- private func createJWTWrapper(subscriptionAccount: Account, dappUrl: String) async throws -> SubscriptionJWTPayload.Wrapper {
+ private func createJWTWrapper(dappPubKey: DIDKey, subscriptionAccount: Account, dappUrl: String) async throws -> NotifySubscriptionPayload.Wrapper {
let types = try await subscriptionScopeProvider.getSubscriptionScope(dappUrl: dappUrl)
let scope = types.map{$0.name}.joined(separator: " ")
- let jwtPayload = SubscriptionJWTPayload(keyserver: keyserverURL, subscriptionAccount: subscriptionAccount, dappUrl: dappUrl, scope: scope)
+ let jwtPayload = NotifySubscriptionPayload(dappPubKey: dappPubKey, keyserver: keyserverURL, subscriptionAccount: subscriptionAccount, dappUrl: dappUrl, scope: scope)
return try identityClient.signAndCreateWrapper(
payload: jwtPayload,
account: subscriptionAccount
diff --git a/Sources/WalletConnectPush/Client/Wallet/ProtocolEngine/wc_pushSubscribe/PushSubscribeResponseSubscriber.swift b/Sources/WalletConnectNotify/Client/Wallet/ProtocolEngine/wc_pushSubscribe/NotifySubscribeResponseSubscriber.swift
similarity index 66%
rename from Sources/WalletConnectPush/Client/Wallet/ProtocolEngine/wc_pushSubscribe/PushSubscribeResponseSubscriber.swift
rename to Sources/WalletConnectNotify/Client/Wallet/ProtocolEngine/wc_pushSubscribe/NotifySubscribeResponseSubscriber.swift
index d8c5fe66c..b663aaa14 100644
--- a/Sources/WalletConnectPush/Client/Wallet/ProtocolEngine/wc_pushSubscribe/PushSubscribeResponseSubscriber.swift
+++ b/Sources/WalletConnectNotify/Client/Wallet/ProtocolEngine/wc_pushSubscribe/NotifySubscribeResponseSubscriber.swift
@@ -1,7 +1,7 @@
import Foundation
import Combine
-class PushSubscribeResponseSubscriber {
+class NotifySubscribeResponseSubscriber {
enum Errors: Error {
case couldNotCreateSubscription
}
@@ -16,7 +16,7 @@ class PushSubscribeResponseSubscriber {
private let kms: KeyManagementServiceProtocol
private var publishers = [AnyCancellable]()
private let logger: ConsoleLogging
- private let pushStorage: PushStorage
+ private let notifyStorage: NotifyStorage
private let groupKeychainStorage: KeychainStorageProtocol
private let dappsMetadataStore: CodableStore
private let subscriptionScopeProvider: SubscriptionScopeProvider
@@ -25,7 +25,7 @@ class PushSubscribeResponseSubscriber {
kms: KeyManagementServiceProtocol,
logger: ConsoleLogging,
groupKeychainStorage: KeychainStorageProtocol,
- pushStorage: PushStorage,
+ notifyStorage: NotifyStorage,
dappsMetadataStore: CodableStore,
subscriptionScopeProvider: SubscriptionScopeProvider
) {
@@ -33,65 +33,69 @@ class PushSubscribeResponseSubscriber {
self.kms = kms
self.logger = logger
self.groupKeychainStorage = groupKeychainStorage
- self.pushStorage = pushStorage
+ self.notifyStorage = notifyStorage
self.dappsMetadataStore = dappsMetadataStore
self.subscriptionScopeProvider = subscriptionScopeProvider
subscribeForSubscriptionResponse()
}
private func subscribeForSubscriptionResponse() {
- let protocolMethod = PushSubscribeProtocolMethod()
+ let protocolMethod = NotifySubscribeProtocolMethod()
networkingInteractor.responseSubscription(on: protocolMethod)
- .sink {[unowned self] (payload: ResponseSubscriptionPayload) in
+ .sink { [unowned self] (payload: ResponseSubscriptionPayload) in
Task(priority: .high) {
- logger.debug("PushSubscribeResponseSubscriber: Received Push Subscribe response")
+ logger.debug("Received Notify Subscribe response")
+
+ guard
+ let (responsePayload, _) = try? NotifySubscriptionResponsePayload.decodeAndVerify(from: payload.response)
+ else { fatalError() /* TODO: Handle error */ }
guard let responseKeys = kms.getAgreementSecret(for: payload.topic) else {
- logger.debug("PushSubscribeResponseSubscriber: no symmetric key for topic \(payload.topic)")
+ logger.debug("No symmetric key for topic \(payload.topic)")
return subscriptionErrorSubject.send(Errors.couldNotCreateSubscription)
}
// get keypair Y
let pubKeyY = responseKeys.publicKey
- let peerPubKeyZ = payload.response.publicKey
+ let peerPubKeyZ = responsePayload.publicKey.hexString
var account: Account!
var metadata: AppMetadata!
- var pushSubscriptionTopic: String!
+ var notifySubscriptionTopic: String!
var subscribedTypes: Set!
var agreementKeysP: AgreementKeys!
- let (subscriptionPayload, claims) = try SubscriptionJWTPayload.decodeAndVerify(from: payload.request)
+ let (subscriptionPayload, claims) = try NotifySubscriptionPayload.decodeAndVerify(from: payload.request)
let subscribedScope = subscriptionPayload.scope
.components(separatedBy: " ")
do {
// generate symm key P
agreementKeysP = try kms.performKeyAgreement(selfPublicKey: pubKeyY, peerPublicKey: peerPubKeyZ)
- pushSubscriptionTopic = agreementKeysP.derivedTopic()
- try kms.setAgreementSecret(agreementKeysP, topic: pushSubscriptionTopic)
- try groupKeychainStorage.add(agreementKeysP, forKey: pushSubscriptionTopic)
+ notifySubscriptionTopic = agreementKeysP.derivedTopic()
+ try kms.setAgreementSecret(agreementKeysP, topic: notifySubscriptionTopic)
+ try groupKeychainStorage.add(agreementKeysP, forKey: notifySubscriptionTopic)
account = try Account(DIDPKHString: claims.sub)
metadata = try dappsMetadataStore.get(key: payload.topic)
let availableTypes = try await subscriptionScopeProvider.getSubscriptionScope(dappUrl: metadata!.url)
subscribedTypes = availableTypes.filter{subscribedScope.contains($0.name)}
- logger.debug("PushSubscribeResponseSubscriber: subscribing push subscription topic: \(pushSubscriptionTopic!)")
- try await networkingInteractor.subscribe(topic: pushSubscriptionTopic)
+ logger.debug("subscribing notify subscription topic: \(notifySubscriptionTopic!)")
+ try await networkingInteractor.subscribe(topic: notifySubscriptionTopic)
} catch {
- logger.debug("PushSubscribeResponseSubscriber: error: \(error)")
+ logger.debug("error: \(error)")
return subscriptionErrorSubject.send(Errors.couldNotCreateSubscription)
}
guard let metadata = metadata else {
- logger.debug("PushSubscribeResponseSubscriber: no metadata for topic: \(pushSubscriptionTopic!)")
+ logger.debug("No metadata for topic: \(notifySubscriptionTopic!)")
return subscriptionErrorSubject.send(Errors.couldNotCreateSubscription)
}
dappsMetadataStore.delete(forKey: payload.topic)
let expiry = Date(timeIntervalSince1970: TimeInterval(claims.exp))
let scope: [String: ScopeValue] = subscribedTypes.reduce(into: [:]) { $0[$1.name] = ScopeValue(description: $1.description, enabled: true) }
- let pushSubscription = PushSubscription(topic: pushSubscriptionTopic, account: account, relay: RelayProtocolOptions(protocol: "irn", data: nil), metadata: metadata, scope: scope, expiry: expiry, symKey: agreementKeysP.sharedKey.hexRepresentation)
+ let notifySubscription = NotifySubscription(topic: notifySubscriptionTopic, account: account, relay: RelayProtocolOptions(protocol: "irn", data: nil), metadata: metadata, scope: scope, expiry: expiry, symKey: agreementKeysP.sharedKey.hexRepresentation)
- try await pushStorage.setSubscription(pushSubscription)
+ notifyStorage.setSubscription(notifySubscription)
- logger.debug("PushSubscribeResponseSubscriber: unsubscribing response topic: \(payload.topic)")
+ logger.debug("Unsubscribing response topic: \(payload.topic)")
networkingInteractor.unsubscribe(topic: payload.topic)
}
}.store(in: &publishers)
diff --git a/Sources/WalletConnectPush/Client/Wallet/SubscriptionScopeProvider.swift b/Sources/WalletConnectNotify/Client/Wallet/SubscriptionScopeProvider.swift
similarity index 92%
rename from Sources/WalletConnectPush/Client/Wallet/SubscriptionScopeProvider.swift
rename to Sources/WalletConnectNotify/Client/Wallet/SubscriptionScopeProvider.swift
index a2140addd..a362dd4e6 100644
--- a/Sources/WalletConnectPush/Client/Wallet/SubscriptionScopeProvider.swift
+++ b/Sources/WalletConnectNotify/Client/Wallet/SubscriptionScopeProvider.swift
@@ -12,7 +12,7 @@ class SubscriptionScopeProvider {
if let availableScope = cache[dappUrl] {
return availableScope
}
- guard let scopeUrl = URL(string: "\(dappUrl)/.well-known/wc-push-config.json") else { throw Errors.invalidUrl }
+ guard let scopeUrl = URL(string: "\(dappUrl)/.well-known/wc-notify-config.json") else { throw Errors.invalidUrl }
let (data, _) = try await URLSession.shared.data(from: scopeUrl)
let config = try JSONDecoder().decode(NotificationConfig.self, from: data)
let availableScope = Set(config.types)
diff --git a/Sources/WalletConnectPush/Client/Wallet/SubscriptionsAutoUpdater.swift b/Sources/WalletConnectNotify/Client/Wallet/SubscriptionsAutoUpdater.swift
similarity index 83%
rename from Sources/WalletConnectPush/Client/Wallet/SubscriptionsAutoUpdater.swift
rename to Sources/WalletConnectNotify/Client/Wallet/SubscriptionsAutoUpdater.swift
index d6e939a5b..4eff66e89 100644
--- a/Sources/WalletConnectPush/Client/Wallet/SubscriptionsAutoUpdater.swift
+++ b/Sources/WalletConnectNotify/Client/Wallet/SubscriptionsAutoUpdater.swift
@@ -4,19 +4,19 @@ import Foundation
class SubscriptionsAutoUpdater {
private let notifyUpdateRequester: NotifyUpdateRequesting
private let logger: ConsoleLogging
- private let pushStorage: PushStoring
+ private let notifyStorage: NotifyStoring
init(notifyUpdateRequester: NotifyUpdateRequesting,
logger: ConsoleLogging,
- pushStorage: PushStoring) {
+ notifyStorage: NotifyStoring) {
self.notifyUpdateRequester = notifyUpdateRequester
self.logger = logger
- self.pushStorage = pushStorage
+ self.notifyStorage = notifyStorage
updateSubscriptionsIfNeeded()
}
private func updateSubscriptionsIfNeeded() {
- for subscription in pushStorage.getSubscriptions() {
+ for subscription in notifyStorage.getSubscriptions() {
if shouldUpdate(subscription: subscription) {
let scope = Set(subscription.scope.filter{ $0.value.enabled == true }.keys)
let topic = subscription.topic
@@ -31,7 +31,7 @@ class SubscriptionsAutoUpdater {
}
}
- private func shouldUpdate(subscription: PushSubscription) -> Bool {
+ private func shouldUpdate(subscription: NotifySubscription) -> Bool {
let currentDate = Date()
let calendar = Calendar.current
let expiryDate = subscription.expiry
diff --git a/Sources/WalletConnectNotify/Client/Wallet/WebDidResolver.swift b/Sources/WalletConnectNotify/Client/Wallet/WebDidResolver.swift
new file mode 100644
index 000000000..2f27c2693
--- /dev/null
+++ b/Sources/WalletConnectNotify/Client/Wallet/WebDidResolver.swift
@@ -0,0 +1,29 @@
+import Foundation
+
+final class WebDidResolver {
+
+ func resolvePublicKey(dappUrl: String) async throws -> AgreementPublicKey {
+ let didDoc = try await resolveDidDoc(domainUrl: dappUrl)
+ guard let keyAgreement = didDoc.keyAgreement.first else { throw Errors.didDocDoesNotContainKeyAgreement }
+ guard let verificationMethod = didDoc.verificationMethod.first(where: { verificationMethod in verificationMethod.id == keyAgreement }) else { throw Errors.noVerificationMethodForKey }
+ guard verificationMethod.publicKeyJwk.crv == .X25519 else { throw Errors.unsupportedCurve}
+ let pubKeyBase64Url = verificationMethod.publicKeyJwk.x
+ return try AgreementPublicKey(base64url: pubKeyBase64Url)
+ }
+}
+
+private extension WebDidResolver {
+
+ enum Errors: Error {
+ case invalidUrl
+ case didDocDoesNotContainKeyAgreement
+ case noVerificationMethodForKey
+ case unsupportedCurve
+ }
+
+ func resolveDidDoc(domainUrl: String) async throws -> WebDidDoc {
+ guard let didDocUrl = URL(string: "\(domainUrl)/.well-known/did.json") else { throw Errors.invalidUrl }
+ let (data, _) = try await URLSession.shared.data(from: didDocUrl)
+ return try JSONDecoder().decode(WebDidDoc.self, from: data)
+ }
+}
diff --git a/Sources/WalletConnectNotify/Notify.swift b/Sources/WalletConnectNotify/Notify.swift
new file mode 100644
index 000000000..3a8a0bb14
--- /dev/null
+++ b/Sources/WalletConnectNotify/Notify.swift
@@ -0,0 +1,26 @@
+import Foundation
+
+public class Notify {
+ public static var instance: NotifyClient = {
+ guard let config = Notify.config else {
+ fatalError("Error - you must call Notify.configure(_:) before accessing the shared wallet instance.")
+ }
+ Push.configure(pushHost: config.pushHost, environment: config.environment)
+ return NotifyClientFactory.create(
+ networkInteractor: Networking.interactor,
+ pairingRegisterer: Pair.registerer,
+ pushClient: Push.instance,
+ crypto: config.crypto
+ )
+ }()
+
+ private static var config: Config?
+
+ private init() { }
+
+ /// Wallet's configuration method
+ static public func configure(pushHost: String = "echo.walletconnect.com", environment: APNSEnvironment, crypto: CryptoProvider) {
+ Notify.config = Notify.Config(pushHost: pushHost, environment: environment, crypto: crypto)
+ }
+
+}
diff --git a/Sources/WalletConnectEcho/EchoConfig.swift b/Sources/WalletConnectNotify/NotifyConfig.swift
similarity index 51%
rename from Sources/WalletConnectEcho/EchoConfig.swift
rename to Sources/WalletConnectNotify/NotifyConfig.swift
index 595ea2b73..479c830ab 100644
--- a/Sources/WalletConnectEcho/EchoConfig.swift
+++ b/Sources/WalletConnectNotify/NotifyConfig.swift
@@ -1,8 +1,9 @@
import Foundation
-extension Echo {
+extension Notify {
struct Config {
- let echoHost: String
+ let pushHost: String
let environment: APNSEnvironment
+ let crypto: CryptoProvider
}
}
diff --git a/Sources/WalletConnectNotify/NotifyImports.swift b/Sources/WalletConnectNotify/NotifyImports.swift
new file mode 100644
index 000000000..74fcfa250
--- /dev/null
+++ b/Sources/WalletConnectNotify/NotifyImports.swift
@@ -0,0 +1,6 @@
+#if !CocoaPods
+@_exported import WalletConnectPairing
+@_exported import WalletConnectPush
+@_exported import WalletConnectIdentity
+@_exported import WalletConnectSigner
+#endif
diff --git a/Sources/WalletConnectNotify/NotifyStorageIdntifiers.swift b/Sources/WalletConnectNotify/NotifyStorageIdntifiers.swift
new file mode 100644
index 000000000..fb1b21c53
--- /dev/null
+++ b/Sources/WalletConnectNotify/NotifyStorageIdntifiers.swift
@@ -0,0 +1,9 @@
+import Foundation
+
+enum NotifyStorageIdntifiers {
+ static let notifySubscription = "com.walletconnect.notify.notifySubscription"
+
+ static let notifyMessagesRecords = "com.walletconnect.sdk.notifyMessagesRecords"
+ static let dappsMetadataStore = "com.walletconnect.sdk.dappsMetadataStore"
+ static let coldStartStore = "com.walletconnect.sdk.coldStartStore"
+}
diff --git a/Sources/WalletConnectPush/ProtocolMethods/PushDeleteProtocolMethod.swift b/Sources/WalletConnectNotify/ProtocolMethods/NotifyDeleteProtocolMethod.swift
similarity index 64%
rename from Sources/WalletConnectPush/ProtocolMethods/PushDeleteProtocolMethod.swift
rename to Sources/WalletConnectNotify/ProtocolMethods/NotifyDeleteProtocolMethod.swift
index dd90fe7e2..79e123797 100644
--- a/Sources/WalletConnectPush/ProtocolMethods/PushDeleteProtocolMethod.swift
+++ b/Sources/WalletConnectNotify/ProtocolMethods/NotifyDeleteProtocolMethod.swift
@@ -1,7 +1,7 @@
import Foundation
-struct PushDeleteProtocolMethod: ProtocolMethod {
- let method: String = "wc_pushDelete"
+struct NotifyDeleteProtocolMethod: ProtocolMethod {
+ let method: String = "wc_notifyDelete"
let requestConfig = RelayConfig(tag: 4004, prompt: false, ttl: 86400)
diff --git a/Sources/WalletConnectPush/ProtocolMethods/PushMessageProtocolMethod.swift b/Sources/WalletConnectNotify/ProtocolMethods/NotifyMessageProtocolMethod.swift
similarity index 58%
rename from Sources/WalletConnectPush/ProtocolMethods/PushMessageProtocolMethod.swift
rename to Sources/WalletConnectNotify/ProtocolMethods/NotifyMessageProtocolMethod.swift
index 6345d1dc8..361aec841 100644
--- a/Sources/WalletConnectPush/ProtocolMethods/PushMessageProtocolMethod.swift
+++ b/Sources/WalletConnectNotify/ProtocolMethods/NotifyMessageProtocolMethod.swift
@@ -1,9 +1,9 @@
import Foundation
-struct PushMessageProtocolMethod: ProtocolMethod {
- let method: String = "wc_pushMessage"
+struct NotifyMessageProtocolMethod: ProtocolMethod {
+ let method: String = "wc_notifyMessage"
let requestConfig: RelayConfig = RelayConfig(tag: 4002, prompt: true, ttl: 2592000)
- let responseConfig: RelayConfig = RelayConfig(tag: 4003, prompt: true, ttl: 2592000)
+ let responseConfig: RelayConfig = RelayConfig(tag: 4003, prompt: false, ttl: 2592000)
}
diff --git a/Sources/WalletConnectNotify/ProtocolMethods/NotifySubscribeProtocolMethod.swift b/Sources/WalletConnectNotify/ProtocolMethods/NotifySubscribeProtocolMethod.swift
new file mode 100644
index 000000000..efb256dd7
--- /dev/null
+++ b/Sources/WalletConnectNotify/ProtocolMethods/NotifySubscribeProtocolMethod.swift
@@ -0,0 +1,10 @@
+
+import Foundation
+
+struct NotifySubscribeProtocolMethod: ProtocolMethod {
+ let method: String = "wc_notifySubscribe"
+
+ let requestConfig: RelayConfig = RelayConfig(tag: 4000, prompt: false, ttl: 86400)
+
+ let responseConfig: RelayConfig = RelayConfig(tag: 4001, prompt: false, ttl: 86400)
+}
diff --git a/Sources/WalletConnectPush/ProtocolMethods/NotifyUpdateProtocolMethod.swift b/Sources/WalletConnectNotify/ProtocolMethods/NotifyUpdateProtocolMethod.swift
similarity index 69%
rename from Sources/WalletConnectPush/ProtocolMethods/NotifyUpdateProtocolMethod.swift
rename to Sources/WalletConnectNotify/ProtocolMethods/NotifyUpdateProtocolMethod.swift
index 198ec859f..01ed5a6d8 100644
--- a/Sources/WalletConnectPush/ProtocolMethods/NotifyUpdateProtocolMethod.swift
+++ b/Sources/WalletConnectNotify/ProtocolMethods/NotifyUpdateProtocolMethod.swift
@@ -2,10 +2,10 @@
import Foundation
struct NotifyUpdateProtocolMethod: ProtocolMethod {
- let method: String = "wc_pushUpdate"
+ let method: String = "wc_notifyUpdate"
- let requestConfig: RelayConfig = RelayConfig(tag: 4008, prompt: true, ttl: 86400)
+ let requestConfig: RelayConfig = RelayConfig(tag: 4008, prompt: false, ttl: 86400)
- let responseConfig: RelayConfig = RelayConfig(tag: 4009, prompt: true, ttl: 86400)
+ let responseConfig: RelayConfig = RelayConfig(tag: 4009, prompt: false, ttl: 86400)
}
diff --git a/Sources/WalletConnectNotify/RPCRequests/NotifyDeleteParams.swift b/Sources/WalletConnectNotify/RPCRequests/NotifyDeleteParams.swift
new file mode 100644
index 000000000..7b998c753
--- /dev/null
+++ b/Sources/WalletConnectNotify/RPCRequests/NotifyDeleteParams.swift
@@ -0,0 +1,10 @@
+import Foundation
+
+public struct NotifyDeleteParams: Codable {
+ let code: Int
+ let message: String
+
+ static var userDisconnected: NotifyDeleteParams {
+ return NotifyDeleteParams(code: 6000, message: "User Disconnected")
+ }
+}
diff --git a/Sources/WalletConnectPush/RPCRequests/NotifyProposeParams.swift b/Sources/WalletConnectNotify/RPCRequests/NotifyProposeParams.swift
similarity index 100%
rename from Sources/WalletConnectPush/RPCRequests/NotifyProposeParams.swift
rename to Sources/WalletConnectNotify/RPCRequests/NotifyProposeParams.swift
diff --git a/Sources/WalletConnectPush/RPCRequests/NotifyProposeResponseParams.swift b/Sources/WalletConnectNotify/RPCRequests/NotifyProposeResponseParams.swift
similarity index 100%
rename from Sources/WalletConnectPush/RPCRequests/NotifyProposeResponseParams.swift
rename to Sources/WalletConnectNotify/RPCRequests/NotifyProposeResponseParams.swift
diff --git a/Sources/WalletConnectPush/RPCRequests/SubscribeResponseParams.swift b/Sources/WalletConnectNotify/RPCRequests/SubscribeResponseParams.swift
similarity index 100%
rename from Sources/WalletConnectPush/RPCRequests/SubscribeResponseParams.swift
rename to Sources/WalletConnectNotify/RPCRequests/SubscribeResponseParams.swift
diff --git a/Sources/WalletConnectPush/Types/NotificationConfig.swift b/Sources/WalletConnectNotify/Types/NotificationConfig.swift
similarity index 99%
rename from Sources/WalletConnectPush/Types/NotificationConfig.swift
rename to Sources/WalletConnectNotify/Types/NotificationConfig.swift
index c53e9b674..f17133c93 100644
--- a/Sources/WalletConnectPush/Types/NotificationConfig.swift
+++ b/Sources/WalletConnectNotify/Types/NotificationConfig.swift
@@ -5,5 +5,4 @@ struct NotificationConfig: Codable {
let version: Int
let lastModified: TimeInterval
let types: [NotificationType]
-
}
diff --git a/Sources/WalletConnectPush/Types/NotificationType.swift b/Sources/WalletConnectNotify/Types/NotificationType.swift
similarity index 100%
rename from Sources/WalletConnectPush/Types/NotificationType.swift
rename to Sources/WalletConnectNotify/Types/NotificationType.swift
diff --git a/Sources/WalletConnectPush/Types/PushError.swift b/Sources/WalletConnectNotify/Types/NotifyError.swift
similarity index 90%
rename from Sources/WalletConnectPush/Types/PushError.swift
rename to Sources/WalletConnectNotify/Types/NotifyError.swift
index 82c16c00e..31d567893 100644
--- a/Sources/WalletConnectPush/Types/PushError.swift
+++ b/Sources/WalletConnectNotify/Types/NotifyError.swift
@@ -1,13 +1,13 @@
import Foundation
-public enum PushError: Codable, Equatable, Error {
+public enum NotifyError: Codable, Equatable, Error {
case userRejeted
case userHasExistingSubscription
case methodUnsupported
case registerSignatureRejected
}
-extension PushError: Reason {
+extension NotifyError: Reason {
init?(code: Int) {
switch code {
@@ -41,7 +41,7 @@ extension PushError: Reason {
case .methodUnsupported:
return "Method Unsupported"
case .userRejeted:
- return "Push request rejected"
+ return "Notify request rejected"
case .userHasExistingSubscription:
return "User Has Existing Subscription"
case .registerSignatureRejected:
diff --git a/Sources/WalletConnectPush/Types/PushMessage.swift b/Sources/WalletConnectNotify/Types/NotifyMessage.swift
similarity index 88%
rename from Sources/WalletConnectPush/Types/PushMessage.swift
rename to Sources/WalletConnectNotify/Types/NotifyMessage.swift
index d48604937..783721bbd 100644
--- a/Sources/WalletConnectPush/Types/PushMessage.swift
+++ b/Sources/WalletConnectNotify/Types/NotifyMessage.swift
@@ -1,6 +1,6 @@
import Foundation
-public struct PushMessage: Codable, Equatable {
+public struct NotifyMessage: Codable, Equatable {
public let title: String
public let body: String
public let icon: String
diff --git a/Sources/WalletConnectNotify/Types/NotifyRequest.swift b/Sources/WalletConnectNotify/Types/NotifyRequest.swift
new file mode 100644
index 000000000..be2a15c42
--- /dev/null
+++ b/Sources/WalletConnectNotify/Types/NotifyRequest.swift
@@ -0,0 +1,3 @@
+import Foundation
+
+public typealias NotifyRequest = (id: RPCID, account: Account, metadata: AppMetadata)
diff --git a/Sources/WalletConnectPush/Types/PushSubscription.swift b/Sources/WalletConnectNotify/Types/NotifySubscription.swift
similarity index 89%
rename from Sources/WalletConnectPush/Types/PushSubscription.swift
rename to Sources/WalletConnectNotify/Types/NotifySubscription.swift
index 756d33781..b44189c80 100644
--- a/Sources/WalletConnectPush/Types/PushSubscription.swift
+++ b/Sources/WalletConnectNotify/Types/NotifySubscription.swift
@@ -1,6 +1,6 @@
import Foundation
-public struct PushSubscription: DatabaseObject {
+public struct NotifySubscription: DatabaseObject {
public let topic: String
public let account: Account
public let relay: RelayProtocolOptions
diff --git a/Sources/WalletConnectNotify/Types/NotifySubscriptionResult.swift b/Sources/WalletConnectNotify/Types/NotifySubscriptionResult.swift
new file mode 100644
index 000000000..c9df6a3de
--- /dev/null
+++ b/Sources/WalletConnectNotify/Types/NotifySubscriptionResult.swift
@@ -0,0 +1,7 @@
+
+import Foundation
+
+public struct NotifySubscriptionResult: Equatable, Codable {
+ public let notifySubscription: NotifySubscription
+ public let subscriptionAuth: String
+}
diff --git a/Sources/WalletConnectNotify/Types/Payload/NotifyDeletePayload.swift b/Sources/WalletConnectNotify/Types/Payload/NotifyDeletePayload.swift
new file mode 100644
index 000000000..62aa74204
--- /dev/null
+++ b/Sources/WalletConnectNotify/Types/Payload/NotifyDeletePayload.swift
@@ -0,0 +1,77 @@
+import Foundation
+
+struct NotifyDeletePayload: JWTClaimsCodable {
+
+ struct Claims: JWTClaims {
+ /// Timestamp when JWT was issued
+ let iat: UInt64
+ /// Timestamp when JWT must expire
+ let exp: UInt64
+ /// Key server URL
+ let ksu: String
+ /// Description of action intent. Must be equal to `notify_delete`
+ let act: String?
+
+ /// `did:key` of an identity key. Enables to resolve attached blockchain account.
+ let iss: String
+ /// `did:key` of an identity key. Enables to resolve associated Dapp domain used.
+ let aud: String
+ /// Reason for deleting the subscription
+ let sub: String
+ /// Dapp's domain url
+ let app: String
+
+ static var action: String? {
+ return "notify_delete"
+ }
+ }
+
+ struct Wrapper: JWTWrapper {
+ let deleteAuth: String
+
+ init(jwtString: String) {
+ self.deleteAuth = jwtString
+ }
+
+ var jwtString: String {
+ return deleteAuth
+ }
+ }
+
+ let keyserver: URL
+ let dappPubKey: DIDKey
+ let reason: String
+ let app: String
+
+ init(
+ keyserver: URL,
+ dappPubKey: DIDKey,
+ reason: String,
+ app: String
+ ) {
+ self.keyserver = keyserver
+ self.dappPubKey = dappPubKey
+ self.reason = reason
+ self.app = app
+ }
+
+ init(claims: Claims) throws {
+ self.keyserver = try claims.ksu.asURL()
+ self.dappPubKey = try DIDKey(did: claims.aud)
+ self.reason = claims.sub
+ self.app = claims.app
+ }
+
+ func encode(iss: String) throws -> Claims {
+ return Claims(
+ iat: defaultIat(),
+ exp: expiry(days: 1),
+ ksu: keyserver.absoluteString,
+ act: Claims.action,
+ iss: iss,
+ aud: dappPubKey.did(variant: .ED25519),
+ sub: reason,
+ app: app
+ )
+ }
+}
diff --git a/Sources/WalletConnectNotify/Types/Payload/NotifyDeleteResponsePayload.swift b/Sources/WalletConnectNotify/Types/Payload/NotifyDeleteResponsePayload.swift
new file mode 100644
index 000000000..338ff414e
--- /dev/null
+++ b/Sources/WalletConnectNotify/Types/Payload/NotifyDeleteResponsePayload.swift
@@ -0,0 +1,77 @@
+import Foundation
+
+struct NotifyDeleteResponsePayload: JWTClaimsCodable {
+
+ struct Claims: JWTClaims {
+ /// Timestamp when JWT was issued
+ let iat: UInt64
+ /// Timestamp when JWT must expire
+ let exp: UInt64
+ /// Key server URL
+ let ksu: String
+ /// Description of action intent. Must be equal to `notify_delete_response`
+ let act: String?
+
+ /// `did:key` of an identity key. Enables to resolve associated Dapp domain used
+ let iss: String
+ /// `did:key` of an identity key. Enables to resolve attached blockchain account.
+ let aud: String
+ /// Hash of the existing subscription payload
+ let sub: String
+ /// Dapp's domain url
+ let app: String
+
+ static var action: String? {
+ return "notify_delete_response"
+ }
+ }
+
+ struct Wrapper: JWTWrapper {
+ let responseAuth: String
+
+ init(jwtString: String) {
+ self.responseAuth = jwtString
+ }
+
+ var jwtString: String {
+ return responseAuth
+ }
+ }
+
+ let keyserver: URL
+ let selfPubKey: DIDKey
+ let subscriptionHash: String
+ let app: String
+
+ init(
+ keyserver: URL,
+ selfPubKey: DIDKey,
+ subscriptionHash: String,
+ app: String
+ ) {
+ self.keyserver = keyserver
+ self.selfPubKey = selfPubKey
+ self.subscriptionHash = subscriptionHash
+ self.app = app
+ }
+
+ init(claims: Claims) throws {
+ self.keyserver = try claims.ksu.asURL()
+ self.selfPubKey = try DIDKey(did: claims.aud)
+ self.subscriptionHash = claims.sub
+ self.app = claims.app
+ }
+
+ func encode(iss: String) throws -> Claims {
+ return Claims(
+ iat: defaultIat(),
+ exp: expiry(days: 1),
+ ksu: keyserver.absoluteString,
+ act: Claims.action,
+ iss: iss,
+ aud: selfPubKey.did(variant: .ED25519),
+ sub: subscriptionHash,
+ app: app
+ )
+ }
+}
diff --git a/Sources/WalletConnectNotify/Types/Payload/NotifyMessagePayload.swift b/Sources/WalletConnectNotify/Types/Payload/NotifyMessagePayload.swift
new file mode 100644
index 000000000..e932f80fe
--- /dev/null
+++ b/Sources/WalletConnectNotify/Types/Payload/NotifyMessagePayload.swift
@@ -0,0 +1,89 @@
+import Foundation
+
+struct NotifyMessagePayload: JWTClaimsCodable {
+
+ struct Claims: JWTClaims {
+ /// Timestamp when JWT was issued
+ let iat: UInt64
+ /// Timestamp when JWT must expire
+ let exp: UInt64
+ /// Key server URL
+ let ksu: String
+ /// Action intent (must be `notify_message`)
+ let act: String?
+
+ /// `did:key` of an identity key. Enables to resolve associated Dapp domain used. diddoc authentication key
+ let iss: String
+ /// Blockchain account `did:pkh`
+ let aud: String
+ /// Subscription ID (sha256 hash of subscriptionAuth)
+ let sub: String
+ /// Dapp domain url
+ let app: String
+ /// Message object
+ let msg: NotifyMessage
+
+ static var action: String? {
+ return "notify_message"
+ }
+ }
+
+ struct Wrapper: JWTWrapper {
+ let messageAuth: String
+
+ init(jwtString: String) {
+ self.messageAuth = jwtString
+ }
+
+ var jwtString: String {
+ return messageAuth
+ }
+ }
+
+ let castServerPubKey: DIDKey
+ let keyserver: URL
+ let account: Account
+ let subscriptionId: String
+ let app: String
+ let message: NotifyMessage
+
+ init(
+ castServerPubKey: DIDKey,
+ keyserver: URL,
+ account: Account,
+ subscriptionId: String,
+ app: String,
+ message: NotifyMessage
+ ) {
+ self.castServerPubKey = castServerPubKey
+ self.keyserver = keyserver
+ self.account = account
+ self.subscriptionId = subscriptionId
+ self.app = app
+ self.message = message
+ }
+
+ init(claims: Claims) throws {
+ self.castServerPubKey = try DIDKey(did: claims.iss)
+ self.keyserver = try claims.ksu.asURL()
+ self.account = try DIDPKH(did: claims.aud).account
+ self.subscriptionId = claims.sub
+ self.app = claims.app
+ self.message = claims.msg
+ }
+
+ func encode(iss: String) throws -> Claims {
+ return Claims(
+ iat: defaultIat(),
+ exp: expiry(days: 1),
+ ksu: keyserver.absoluteString,
+ act: Claims.action,
+ iss: castServerPubKey.multibase(variant: .ED25519),
+ aud: account.did,
+ sub: subscriptionId,
+ app: app,
+ msg: message
+ )
+ }
+
+}
diff --git a/Sources/WalletConnectNotify/Types/Payload/NotifyMessageReceiptPayload.swift b/Sources/WalletConnectNotify/Types/Payload/NotifyMessageReceiptPayload.swift
new file mode 100644
index 000000000..57934d03c
--- /dev/null
+++ b/Sources/WalletConnectNotify/Types/Payload/NotifyMessageReceiptPayload.swift
@@ -0,0 +1,77 @@
+import Foundation
+
+struct NotifyMessageReceiptPayload: JWTClaimsCodable {
+
+ struct Claims: JWTClaims {
+ /// Timestamp when JWT was issued
+ let iat: UInt64
+ /// Timestamp when JWT must expire
+ let exp: UInt64
+ /// Key server URL
+ let ksu: String
+ /// Action intent (must be `notify_receipt`)
+ let act: String?
+
+ /// `did:key` of an identity key. Enables to resolve attached blockchain account.
+ let iss: String
+ /// `did:key` of an identity key. Enables to resolve associated Dapp domain used.
+ let aud: String
+ /// Hash of the stringified notify message object received
+ let sub: String
+ /// Dapp's domain url
+ let app: String
+
+ static var action: String? {
+ return "notify_receipt"
+ }
+ }
+
+ struct Wrapper: JWTWrapper {
+ let receiptAuth: String
+
+ init(jwtString: String) {
+ self.receiptAuth = jwtString
+ }
+
+ var jwtString: String {
+ return receiptAuth
+ }
+ }
+
+ let keyserver: URL
+ let dappPubKey: DIDKey
+ let messageHash: String
+ let app: String
+
+ init(
+ keyserver: URL,
+ dappPubKey: DIDKey,
+ messageHash: String,
+ app: String
+ ) {
+ self.keyserver = keyserver
+ self.dappPubKey = dappPubKey
+ self.messageHash = messageHash
+ self.app = app
+ }
+
+ init(claims: Claims) throws {
+ self.keyserver = try claims.ksu.asURL()
+ self.dappPubKey = try DIDKey(did: claims.aud)
+ self.messageHash = claims.sub
+ self.app = claims.app
+ }
+
+ func encode(iss: String) throws -> Claims {
+ return Claims(
+ iat: defaultIat(),
+ exp: expiry(days: 1),
+ ksu: keyserver.absoluteString,
+ act: Claims.action,
+ iss: iss,
+ aud: dappPubKey.did(variant: .ED25519),
+ sub: messageHash,
+ app: app
+ )
+ }
+}
diff --git a/Sources/WalletConnectNotify/Types/Payload/NotifySubscriptionPayload.swift b/Sources/WalletConnectNotify/Types/Payload/NotifySubscriptionPayload.swift
new file mode 100644
index 000000000..847635da0
--- /dev/null
+++ b/Sources/WalletConnectNotify/Types/Payload/NotifySubscriptionPayload.swift
@@ -0,0 +1,79 @@
+import Foundation
+
+struct NotifySubscriptionPayload: JWTClaimsCodable {
+
+ struct Claims: JWTClaims {
+ /// Timestamp when JWT was issued
+ let iat: UInt64
+ /// Timestamp when JWT must expire
+ let exp: UInt64
+ /// Key server URL
+ let ksu: String
+ /// Description of action intent. Must be equal to `notify_subscription`
+ let act: String?
+
+ /// `did:key` of an identity key. Enables to resolve attached blockchain account.
+ let iss: String
+ /// `did:key` of an identity key. Enables to resolve associated Dapp domain used.
+ let aud: String
+ /// Blockchain account that notify subscription has been proposed for -`did:pkh`
+ let sub: String
+ /// Scope of notification types authorized by the user
+ let scp: String
+ /// Dapp's domain url
+ let app: String
+
+ static var action: String? {
+ return "notify_subscription"
+ }
+ }
+
+ struct Wrapper: JWTWrapper {
+ let subscriptionAuth: String
+
+ init(jwtString: String) {
+ self.subscriptionAuth = jwtString
+ }
+
+ var jwtString: String {
+ return subscriptionAuth
+ }
+ }
+
+ let dappPubKey: DIDKey
+ let keyserver: URL
+ let subscriptionAccount: Account
+ let dappUrl: String
+ let scope: String
+
+ init(dappPubKey: DIDKey, keyserver: URL, subscriptionAccount: Account, dappUrl: String, scope: String) {
+ self.dappPubKey = dappPubKey
+ self.keyserver = keyserver
+ self.subscriptionAccount = subscriptionAccount
+ self.dappUrl = dappUrl
+ self.scope = scope
+ }
+
+ init(claims: Claims) throws {
+ self.dappPubKey = try DIDKey(did: claims.aud)
+ self.keyserver = try claims.ksu.asURL()
+ self.subscriptionAccount = try Account(DIDPKHString: claims.sub)
+ self.dappUrl = claims.app
+ self.scope = claims.scp
+ }
+
+ func encode(iss: String) throws -> Claims {
+ return Claims(
+ iat: defaultIat(),
+ exp: expiry(days: 30),
+ ksu: keyserver.absoluteString,
+ act: Claims.action,
+ iss: iss,
+ aud: dappPubKey.did(variant: .ED25519),
+ sub: subscriptionAccount.did,
+ scp: scope,
+ app: dappUrl
+ )
+ }
+}
+
diff --git a/Sources/WalletConnectNotify/Types/Payload/NotifySubscriptionResponsePayload.swift b/Sources/WalletConnectNotify/Types/Payload/NotifySubscriptionResponsePayload.swift
new file mode 100644
index 000000000..b330882c1
--- /dev/null
+++ b/Sources/WalletConnectNotify/Types/Payload/NotifySubscriptionResponsePayload.swift
@@ -0,0 +1,65 @@
+import Foundation
+
+struct NotifySubscriptionResponsePayload: JWTClaimsCodable {
+
+ struct Claims: JWTClaims {
+ /// timestamp when jwt was issued
+ let iat: UInt64
+ /// timestamp when jwt must expire
+ let exp: UInt64
+ /// Key server URL
+ let ksu: String
+ /// Description of action intent. Must be equal to "notify_subscription_response"
+ let act: String?
+
+ /// `did:key` of an identity key. Allows for the resolution of which Notify server was used.
+ let iss: String
+ /// `did:key` of an identity key. Allows for the resolution of the attached blockchain account.
+ let aud: String
+ /// `did:key` of the public key used for key agreement on the Notify topic
+ let sub: String
+ /// Dapp's domain url
+ let app: String
+
+ static var action: String? {
+ return "notify_subscription_response"
+ }
+ }
+
+ struct Wrapper: JWTWrapper {
+ let responseAuth: String
+
+ init(jwtString: String) {
+ self.responseAuth = jwtString
+ }
+
+ var jwtString: String {
+ return responseAuth
+ }
+ }
+
+ let keyserver: URL
+ let selfPubKey: DIDKey
+ let publicKey: DIDKey
+ let app: String
+
+ init(claims: Claims) throws {
+ self.keyserver = try claims.ksu.asURL()
+ self.selfPubKey = try DIDKey(did: claims.aud)
+ self.publicKey = try DIDKey(did: claims.sub)
+ self.app = claims.app
+ }
+
+ func encode(iss: String) throws -> Claims {
+ return Claims(
+ iat: defaultIat(),
+ exp: expiry(days: 1),
+ ksu: keyserver.absoluteString,
+ act: Claims.action,
+ iss: iss,
+ aud: selfPubKey.did(variant: .ED25519),
+ sub: publicKey.did(variant: .X25519),
+ app: app
+ )
+ }
+}
diff --git a/Sources/WalletConnectNotify/Types/Payload/NotifyUpdatePayload.swift b/Sources/WalletConnectNotify/Types/Payload/NotifyUpdatePayload.swift
new file mode 100644
index 000000000..ef88ebc87
--- /dev/null
+++ b/Sources/WalletConnectNotify/Types/Payload/NotifyUpdatePayload.swift
@@ -0,0 +1,78 @@
+import Foundation
+
+struct NotifyUpdatePayload: JWTClaimsCodable {
+
+ struct Claims: JWTClaims {
+ /// Timestamp when JWT was issued
+ let iat: UInt64
+ /// Timestamp when JWT must expire
+ let exp: UInt64
+ /// Key server URL
+ let ksu: String
+ /// Description of action intent. Must be equal to `notify_update`
+ let act: String?
+
+ /// `did:key` of an identity key. Enables to resolve attached blockchain account.
+ let iss: String
+ /// `did:key` of an identity key. Enables to resolve associated Dapp domain used.
+ let aud: String
+ /// Blockchain account that notify subscription has been proposed for -`did:pkh`
+ let sub: String
+ /// Scope of notification types authorized by the user
+ let scp: String
+ /// Dapp's domain url
+ let app: String
+
+ static var action: String? {
+ return "notify_update"
+ }
+ }
+
+ struct Wrapper: JWTWrapper {
+ let updateAuth: String
+
+ init(jwtString: String) {
+ self.updateAuth = jwtString
+ }
+
+ var jwtString: String {
+ return updateAuth
+ }
+ }
+
+ let dappPubKey: DIDKey
+ let keyserver: URL
+ let subscriptionAccount: Account
+ let dappUrl: String
+ let scope: String
+
+ init(dappPubKey: DIDKey, keyserver: URL, subscriptionAccount: Account, dappUrl: String, scope: String) {
+ self.dappPubKey = dappPubKey
+ self.keyserver = keyserver
+ self.subscriptionAccount = subscriptionAccount
+ self.dappUrl = dappUrl
+ self.scope = scope
+ }
+
+ init(claims: Claims) throws {
+ self.dappPubKey = try DIDKey(did: claims.aud)
+ self.keyserver = try claims.ksu.asURL()
+ self.subscriptionAccount = try Account(DIDPKHString: claims.sub)
+ self.dappUrl = claims.app
+ self.scope = claims.scp
+ }
+
+ func encode(iss: String) throws -> Claims {
+ return Claims(
+ iat: defaultIat(),
+ exp: expiry(days: 30),
+ ksu: keyserver.absoluteString,
+ act: Claims.action,
+ iss: iss,
+ aud: dappPubKey.did(variant: .ED25519),
+ sub: subscriptionAccount.did,
+ scp: scope,
+ app: dappUrl
+ )
+ }
+}
diff --git a/Sources/WalletConnectNotify/Types/Payload/NotifyUpdateResponsePayload.swift b/Sources/WalletConnectNotify/Types/Payload/NotifyUpdateResponsePayload.swift
new file mode 100644
index 000000000..db607ffea
--- /dev/null
+++ b/Sources/WalletConnectNotify/Types/Payload/NotifyUpdateResponsePayload.swift
@@ -0,0 +1,65 @@
+import Foundation
+
+struct NotifyUpdateResponsePayload: JWTClaimsCodable {
+
+ struct Claims: JWTClaims {
+ /// timestamp when jwt was issued
+ let iat: UInt64
+ /// timestamp when jwt must expire
+ let exp: UInt64
+ /// Key server URL
+ let ksu: String
+ /// Description of action intent. Must be equal to "notify_update_response"
+ let act: String?
+
+ /// `did:key` of an identity key. Enables to resolve associated Dapp domain used.
+ let iss: String
+ /// `did:key` of an identity key. Enables to resolve attached blockchain account.
+ let aud: String
+ /// Hash of the new subscription payload
+ let sub: String
+ /// Dapp's domain url
+ let app: String
+
+ static var action: String? {
+ return "notify_update_response"
+ }
+ }
+
+ struct Wrapper: JWTWrapper {
+ let responseAuth: String
+
+ init(jwtString: String) {
+ self.responseAuth = jwtString
+ }
+
+ var jwtString: String {
+ return responseAuth
+ }
+ }
+
+ let keyserver: URL
+ let selfPubKey: DIDKey
+ let subscriptionHash: String
+ let app: String
+
+ init(claims: Claims) throws {
+ self.keyserver = try claims.ksu.asURL()
+ self.selfPubKey = try DIDKey(did: claims.aud)
+ self.subscriptionHash = claims.sub
+ self.app = claims.app
+ }
+
+ func encode(iss: String) throws -> Claims {
+ return Claims(
+ iat: defaultIat(),
+ exp: expiry(days: 1),
+ ksu: keyserver.absoluteString,
+ act: Claims.action,
+ iss: iss,
+ aud: selfPubKey.did(variant: .ED25519),
+ sub: subscriptionHash,
+ app: app
+ )
+ }
+}
diff --git a/Sources/WalletConnectPush/Types/WebDidDoc.swift b/Sources/WalletConnectNotify/Types/WebDidDoc.swift
similarity index 94%
rename from Sources/WalletConnectPush/Types/WebDidDoc.swift
rename to Sources/WalletConnectNotify/Types/WebDidDoc.swift
index aca4975f0..adf0c17f9 100644
--- a/Sources/WalletConnectPush/Types/WebDidDoc.swift
+++ b/Sources/WalletConnectNotify/Types/WebDidDoc.swift
@@ -24,7 +24,8 @@ extension WebDidDoc {
struct PublicKeyJwk: Codable {
enum Curve: String, Codable {
- case X25519 = "X25519"
+ case X25519
+ case Ed25519
}
let kty: String
diff --git a/Sources/WalletConnectPairing/PairingClient.swift b/Sources/WalletConnectPairing/PairingClient.swift
index 389c11354..3eafd2838 100644
--- a/Sources/WalletConnectPairing/PairingClient.swift
+++ b/Sources/WalletConnectPairing/PairingClient.swift
@@ -7,6 +7,7 @@ public class PairingClient: PairingRegisterer, PairingInteracting, PairingClient
}
public let socketConnectionStatusPublisher: AnyPublisher
+ private let pairingStorage: WCPairingStorage
private let walletPairService: WalletPairService
private let appPairService: AppPairService
private let appPairActivateService: AppPairActivationService
@@ -22,20 +23,23 @@ public class PairingClient: PairingRegisterer, PairingInteracting, PairingClient
private let cleanupService: PairingCleanupService
- init(appPairService: AppPairService,
- networkingInteractor: NetworkInteracting,
- logger: ConsoleLogging,
- walletPairService: WalletPairService,
- deletePairingService: DeletePairingService,
- resubscribeService: PairingResubscribeService,
- expirationService: ExpirationService,
- pairingRequestsSubscriber: PairingRequestsSubscriber,
- appPairActivateService: AppPairActivationService,
- cleanupService: PairingCleanupService,
- pingService: PairingPingService,
- socketConnectionStatusPublisher: AnyPublisher,
- pairingsProvider: PairingsProvider
+ init(
+ pairingStorage: WCPairingStorage,
+ appPairService: AppPairService,
+ networkingInteractor: NetworkInteracting,
+ logger: ConsoleLogging,
+ walletPairService: WalletPairService,
+ deletePairingService: DeletePairingService,
+ resubscribeService: PairingResubscribeService,
+ expirationService: ExpirationService,
+ pairingRequestsSubscriber: PairingRequestsSubscriber,
+ appPairActivateService: AppPairActivationService,
+ cleanupService: PairingCleanupService,
+ pingService: PairingPingService,
+ socketConnectionStatusPublisher: AnyPublisher,
+ pairingsProvider: PairingsProvider
) {
+ self.pairingStorage = pairingStorage
self.appPairService = appPairService
self.walletPairService = walletPairService
self.networkingInteractor = networkingInteractor
@@ -81,6 +85,15 @@ public class PairingClient: PairingRegisterer, PairingInteracting, PairingClient
public func activate(pairingTopic: String, peerMetadata: AppMetadata?) {
appPairActivateService.activate(for: pairingTopic, peerMetadata: peerMetadata)
}
+
+ public func setReceived(pairingTopic: String) {
+ guard var pairing = pairingStorage.getPairing(forTopic: pairingTopic) else {
+ return logger.error("Pairing not found for topic: \(pairingTopic)")
+ }
+
+ pairing.receivedRequest()
+ pairingStorage.setPairing(pairing)
+ }
public func getPairings() -> [Pairing] {
pairingsProvider.getPairings()
diff --git a/Sources/WalletConnectPairing/PairingClientFactory.swift b/Sources/WalletConnectPairing/PairingClientFactory.swift
index 8666747ac..ba49bb6af 100644
--- a/Sources/WalletConnectPairing/PairingClientFactory.swift
+++ b/Sources/WalletConnectPairing/PairingClientFactory.swift
@@ -12,8 +12,9 @@ public struct PairingClientFactory {
public static func create(logger: ConsoleLogging, keyValueStorage: KeyValueStorage, keychainStorage: KeychainStorageProtocol, networkingClient: NetworkingInteractor) -> PairingClient {
let pairingStore = PairingStorage(storage: SequenceStore(store: .init(defaults: keyValueStorage, identifier: PairStorageIdentifiers.pairings.rawValue)))
let kms = KeyManagementService(keychain: keychainStorage)
+ let history = RPCHistoryFactory.createForNetwork(keyValueStorage: keyValueStorage)
let appPairService = AppPairService(networkingInteractor: networkingClient, kms: kms, pairingStorage: pairingStore)
- let walletPairService = WalletPairService(networkingInteractor: networkingClient, kms: kms, pairingStorage: pairingStore)
+ let walletPairService = WalletPairService(networkingInteractor: networkingClient, kms: kms, pairingStorage: pairingStore, history: history)
let pairingRequestsSubscriber = PairingRequestsSubscriber(networkingInteractor: networkingClient, pairingStorage: pairingStore, logger: logger)
let pairingsProvider = PairingsProvider(pairingStorage: pairingStore)
let cleanupService = PairingCleanupService(pairingStore: pairingStore, kms: kms)
@@ -24,6 +25,7 @@ public struct PairingClientFactory {
let resubscribeService = PairingResubscribeService(networkInteractor: networkingClient, pairingStorage: pairingStore)
return PairingClient(
+ pairingStorage: pairingStore,
appPairService: appPairService,
networkingInteractor: networkingClient,
logger: logger,
diff --git a/Sources/WalletConnectPairing/PairingRegisterer.swift b/Sources/WalletConnectPairing/PairingRegisterer.swift
index 6aa017038..e09cf9a42 100644
--- a/Sources/WalletConnectPairing/PairingRegisterer.swift
+++ b/Sources/WalletConnectPairing/PairingRegisterer.swift
@@ -7,5 +7,6 @@ public protocol PairingRegisterer {
) -> AnyPublisher, Never>
func activate(pairingTopic: String, peerMetadata: AppMetadata?)
+ func setReceived(pairingTopic: String)
func validatePairingExistance(_ topic: String) throws
}
diff --git a/Sources/WalletConnectPairing/Services/Wallet/WalletPairService.swift b/Sources/WalletConnectPairing/Services/Wallet/WalletPairService.swift
index cfbd6c930..c962b8a41 100644
--- a/Sources/WalletConnectPairing/Services/Wallet/WalletPairService.swift
+++ b/Sources/WalletConnectPairing/Services/Wallet/WalletPairService.swift
@@ -3,34 +3,80 @@ import Foundation
actor WalletPairService {
enum Errors: Error {
case pairingAlreadyExist(topic: String)
+ case networkNotConnected
}
let networkingInteractor: NetworkInteracting
let kms: KeyManagementServiceProtocol
private let pairingStorage: WCPairingStorage
+ private let history: RPCHistory
- init(networkingInteractor: NetworkInteracting,
- kms: KeyManagementServiceProtocol,
- pairingStorage: WCPairingStorage) {
+ init(
+ networkingInteractor: NetworkInteracting,
+ kms: KeyManagementServiceProtocol,
+ pairingStorage: WCPairingStorage,
+ history: RPCHistory
+ ) {
self.networkingInteractor = networkingInteractor
self.kms = kms
self.pairingStorage = pairingStorage
+ self.history = history
}
func pair(_ uri: WalletConnectURI) async throws {
- guard !hasPairing(for: uri.topic) else {
- throw Errors.pairingAlreadyExist(topic: uri.topic)
+ guard try !pairingHasPendingRequest(for: uri.topic) else {
+ return
}
- var pairing = WCPairing(uri: uri)
+
+ let pairing = WCPairing(uri: uri)
let symKey = try SymmetricKey(hex: uri.symKey)
try kms.setSymmetricKey(symKey, for: pairing.topic)
- pairing.activate()
pairingStorage.setPairing(pairing)
+
+ let networkConnectionStatus = await resolveNetworkConnectionStatus()
+ guard networkConnectionStatus == .connected else {
+ throw Errors.networkNotConnected
+ }
+
try await networkingInteractor.subscribe(topic: pairing.topic)
}
+}
+
+// MARK: - Private functions
+extension WalletPairService {
+ func pairingHasPendingRequest(for topic: String) throws -> Bool {
+ guard let pairing = pairingStorage.getPairing(forTopic: topic), pairing.requestReceived else {
+ return false
+ }
+
+ if pairing.active {
+ throw Errors.pairingAlreadyExist(topic: topic)
+ }
+
+ let pendingRequests = history.getPending()
+ .compactMap { record -> RPCRequest? in
+ (record.topic == pairing.topic) ? record.request : nil
+ }
- func hasPairing(for topic: String) -> Bool {
- return pairingStorage.hasPairing(forTopic: topic)
+ if let pendingRequest = pendingRequests.first {
+ networkingInteractor.handleHistoryRequest(topic: topic, request: pendingRequest)
+ return true
+ }
+ return false
+ }
+
+ private func resolveNetworkConnectionStatus() async -> NetworkConnectionStatus {
+ return await withCheckedContinuation { continuation in
+ let cancellable = networkingInteractor.networkConnectionStatusPublisher.sink { value in
+ continuation.resume(returning: value)
+ }
+
+ Task(priority: .high) {
+ await withTaskCancellationHandler {
+ cancellable.cancel()
+ } onCancel: { }
+ }
+ }
}
}
@@ -38,7 +84,8 @@ actor WalletPairService {
extension WalletPairService.Errors: LocalizedError {
var errorDescription: String? {
switch self {
- case .pairingAlreadyExist(let topic): return "Pairing with topic (\(topic)) already exist"
+ case .pairingAlreadyExist(let topic): return "Pairing with topic (\(topic)) is already active"
+ case .networkNotConnected: return "Pairing failed. You seem to be offline"
}
}
}
diff --git a/Sources/WalletConnectPairing/Types/WCPairing.swift b/Sources/WalletConnectPairing/Types/WCPairing.swift
index 6ceb323cd..d87bd8946 100644
--- a/Sources/WalletConnectPairing/Types/WCPairing.swift
+++ b/Sources/WalletConnectPairing/Types/WCPairing.swift
@@ -11,6 +11,7 @@ public struct WCPairing: SequenceObject {
public private (set) var peerMetadata: AppMetadata?
public private (set) var expiryDate: Date
public private (set) var active: Bool
+ public private (set) var requestReceived: Bool
#if DEBUG
public static var dateInitializer: () -> Date = Date.init
@@ -26,11 +27,12 @@ public struct WCPairing: SequenceObject {
30 * .day
}
- public init(topic: String, relay: RelayProtocolOptions, peerMetadata: AppMetadata, isActive: Bool = false, expiryDate: Date) {
+ public init(topic: String, relay: RelayProtocolOptions, peerMetadata: AppMetadata, isActive: Bool = false, requestReceived: Bool = false, expiryDate: Date) {
self.topic = topic
self.relay = relay
self.peerMetadata = peerMetadata
self.active = isActive
+ self.requestReceived = requestReceived
self.expiryDate = expiryDate
}
@@ -38,6 +40,7 @@ public struct WCPairing: SequenceObject {
self.topic = topic
self.relay = RelayProtocolOptions(protocol: "irn", data: nil)
self.active = false
+ self.requestReceived = false
self.expiryDate = Self.dateInitializer().advanced(by: Self.timeToLiveInactive)
}
@@ -45,6 +48,7 @@ public struct WCPairing: SequenceObject {
self.topic = uri.topic
self.relay = uri.relay
self.active = false
+ self.requestReceived = false
self.expiryDate = Self.dateInitializer().advanced(by: Self.timeToLiveInactive)
}
@@ -52,6 +56,10 @@ public struct WCPairing: SequenceObject {
active = true
try? updateExpiry()
}
+
+ public mutating func receivedRequest() {
+ requestReceived = true
+ }
public mutating func updatePeerMetadata(_ metadata: AppMetadata?) {
peerMetadata = metadata
diff --git a/Sources/WalletConnectEcho/APNSEnvironment.swift b/Sources/WalletConnectPush/APNSEnvironment.swift
similarity index 100%
rename from Sources/WalletConnectEcho/APNSEnvironment.swift
rename to Sources/WalletConnectPush/APNSEnvironment.swift
diff --git a/Sources/WalletConnectPush/Client/Common/DeletePushSubscriptionService.swift b/Sources/WalletConnectPush/Client/Common/DeletePushSubscriptionService.swift
deleted file mode 100644
index 2751fca46..000000000
--- a/Sources/WalletConnectPush/Client/Common/DeletePushSubscriptionService.swift
+++ /dev/null
@@ -1,38 +0,0 @@
-import Foundation
-
-class DeletePushSubscriptionService {
- enum Errors: Error {
- case pushSubscriptionNotFound
- }
- private let networkingInteractor: NetworkInteracting
- private let kms: KeyManagementServiceProtocol
- private let logger: ConsoleLogging
- private let pushStorage: PushStorage
-
- init(networkingInteractor: NetworkInteracting,
- kms: KeyManagementServiceProtocol,
- logger: ConsoleLogging,
- pushStorage: PushStorage) {
- self.networkingInteractor = networkingInteractor
- self.kms = kms
- self.logger = logger
- self.pushStorage = pushStorage
- }
-
- func delete(topic: String) async throws {
- let params = PushDeleteParams.userDisconnected
- logger.debug("Will delete push subscription for reason: message: \(params.message) code: \(params.code), topic: \(topic)")
- guard let _ = pushStorage.getSubscription(topic: topic)
- else { throw Errors.pushSubscriptionNotFound}
- let protocolMethod = PushDeleteProtocolMethod()
- try await pushStorage.deleteSubscription(topic: topic)
- pushStorage.deleteMessages(topic: topic)
- let request = RPCRequest(method: protocolMethod.method, params: params)
- try await networkingInteractor.request(request, topic: topic, protocolMethod: protocolMethod)
-
- networkingInteractor.unsubscribe(topic: topic)
- logger.debug("Subscription removed, topic: \(topic)")
-
- kms.deleteSymmetricKey(for: topic)
- }
-}
diff --git a/Sources/WalletConnectPush/Client/Dapp/DappPushClient.swift b/Sources/WalletConnectPush/Client/Dapp/DappPushClient.swift
deleted file mode 100644
index 9c7d820ea..000000000
--- a/Sources/WalletConnectPush/Client/Dapp/DappPushClient.swift
+++ /dev/null
@@ -1,43 +0,0 @@
-import Foundation
-import Combine
-
-public class DappPushClient {
- public var proposalResponsePublisher: AnyPublisher, Never> {
- return notifyProposeResponseSubscriber.proposalResponsePublisher
- }
-
- public var deleteSubscriptionPublisher: AnyPublisher {
- return pushStorage.deleteSubscriptionPublisher
- }
-
- public let logger: ConsoleLogging
-
- private let notifyProposer: NotifyProposer
- private let pushStorage: PushStorage
- private let deletePushSubscriptionSubscriber: DeletePushSubscriptionSubscriber
- private let resubscribeService: PushResubscribeService
- private let notifyProposeResponseSubscriber: NotifyProposeResponseSubscriber
-
- init(logger: ConsoleLogging,
- kms: KeyManagementServiceProtocol,
- pushStorage: PushStorage,
- deletePushSubscriptionSubscriber: DeletePushSubscriptionSubscriber,
- resubscribeService: PushResubscribeService,
- notifyProposer: NotifyProposer,
- notifyProposeResponseSubscriber: NotifyProposeResponseSubscriber) {
- self.logger = logger
- self.pushStorage = pushStorage
- self.deletePushSubscriptionSubscriber = deletePushSubscriptionSubscriber
- self.resubscribeService = resubscribeService
- self.notifyProposer = notifyProposer
- self.notifyProposeResponseSubscriber = notifyProposeResponseSubscriber
- }
-
- public func propose(account: Account, topic: String) async throws {
- try await notifyProposer.propose(topic: topic, account: account)
- }
-
- public func getActiveSubscriptions() -> [PushSubscription] {
- pushStorage.getSubscriptions()
- }
-}
diff --git a/Sources/WalletConnectPush/Client/Dapp/DappPushClientFactory.swift b/Sources/WalletConnectPush/Client/Dapp/DappPushClientFactory.swift
deleted file mode 100644
index 610edff97..000000000
--- a/Sources/WalletConnectPush/Client/Dapp/DappPushClientFactory.swift
+++ /dev/null
@@ -1,41 +0,0 @@
-import Foundation
-
-public struct DappPushClientFactory {
-
- public static func create(metadata: AppMetadata, networkInteractor: NetworkInteracting, syncClient: SyncClient) -> DappPushClient {
- let logger = ConsoleLogger(loggingLevel: .off)
- let keyValueStorage = UserDefaults.standard
- let keychainStorage = KeychainStorage(serviceIdentifier: "com.walletconnect.sdk")
- let groupKeychainStorage = GroupKeychainStorage(serviceIdentifier: "group.com.walletconnect.sdk")
- return DappPushClientFactory.create(
- metadata: metadata,
- logger: logger,
- keyValueStorage: keyValueStorage,
- keychainStorage: keychainStorage,
- groupKeychainStorage: groupKeychainStorage,
- networkInteractor: networkInteractor,
- syncClient: syncClient
- )
- }
-
- static func create(metadata: AppMetadata, logger: ConsoleLogging, keyValueStorage: KeyValueStorage, keychainStorage: KeychainStorageProtocol, groupKeychainStorage: KeychainStorageProtocol, networkInteractor: NetworkInteracting, syncClient: SyncClient) -> DappPushClient {
- let kms = KeyManagementService(keychain: keychainStorage)
- let subscriptionStore: SyncStore = SyncStoreFactory.create(name: PushStorageIdntifiers.pushSubscription, syncClient: syncClient, storage: keyValueStorage)
- let messagesStore = KeyedDatabase(storage: keyValueStorage, identifier: PushStorageIdntifiers.pushMessagesRecords)
- let subscriptionStoreDelegate = PushSubscriptionStoreDelegate(networkingInteractor: networkInteractor, kms: kms, groupKeychainStorage: groupKeychainStorage)
- let pushStorage = PushStorage(subscriptionStore: subscriptionStore, messagesStore: messagesStore, subscriptionStoreDelegate: subscriptionStoreDelegate)
- let deletePushSubscriptionSubscriber = DeletePushSubscriptionSubscriber(networkingInteractor: networkInteractor, kms: kms, logger: logger, pushStorage: pushStorage)
- let resubscribeService = PushResubscribeService(networkInteractor: networkInteractor, pushStorage: pushStorage)
- let notifyProposer = NotifyProposer(networkingInteractor: networkInteractor, kms: kms, appMetadata: metadata, logger: logger)
- let notifyProposeResponseSubscriber = NotifyProposeResponseSubscriber(networkingInteractor: networkInteractor, kms: kms, logger: logger, metadata: metadata)
- return DappPushClient(
- logger: logger,
- kms: kms,
- pushStorage: pushStorage,
- deletePushSubscriptionSubscriber: deletePushSubscriptionSubscriber,
- resubscribeService: resubscribeService,
- notifyProposer: notifyProposer,
- notifyProposeResponseSubscriber: notifyProposeResponseSubscriber
- )
- }
-}
diff --git a/Sources/WalletConnectPush/Client/Dapp/wc_notifyPropose/NotifyProposeResponseSubscriber.swift b/Sources/WalletConnectPush/Client/Dapp/wc_notifyPropose/NotifyProposeResponseSubscriber.swift
deleted file mode 100644
index ee294a384..000000000
--- a/Sources/WalletConnectPush/Client/Dapp/wc_notifyPropose/NotifyProposeResponseSubscriber.swift
+++ /dev/null
@@ -1,71 +0,0 @@
-
-import Foundation
-import Combine
-
-class NotifyProposeResponseSubscriber {
- private let networkingInteractor: NetworkInteracting
- private let metadata: AppMetadata
- private let kms: KeyManagementServiceProtocol
- private let logger: ConsoleLogging
- var proposalResponsePublisher: AnyPublisher, Never> {
- proposalResponsePublisherSubject.eraseToAnyPublisher()
- }
- private let proposalResponsePublisherSubject = PassthroughSubject, Never>()
-
- private var publishers = [AnyCancellable]()
-
- init(networkingInteractor: NetworkInteracting,
- kms: KeyManagementServiceProtocol,
- logger: ConsoleLogging,
- metadata: AppMetadata) {
- self.networkingInteractor = networkingInteractor
- self.kms = kms
- self.logger = logger
- self.metadata = metadata
- subscribeForProposalResponse()
- subscribeForProposalErrors()
- }
-
-
- private func subscribeForProposalResponse() {
- let protocolMethod = NotifyProposeProtocolMethod()
- networkingInteractor.responseSubscription(on: protocolMethod)
- .sink { [unowned self] (payload: ResponseSubscriptionPayload) in
- logger.debug("Received Notify Proposal response")
- Task(priority: .userInitiated) {
- do {
- let pushSubscription = try await handleResponse(payload: payload)
- proposalResponsePublisherSubject.send(.success(pushSubscription))
- } catch {
- logger.error(error)
- }
- }
- }.store(in: &publishers)
- }
-
- func handleResponse(payload: ResponseSubscriptionPayload) async throws -> PushSubscription {
- let jwtWrapper = SubscriptionJWTPayload.Wrapper(jwtString: payload.response.subscriptionAuth)
- let (_, claims) = try SubscriptionJWTPayload.decodeAndVerify(from: jwtWrapper)
- logger.debug("subscriptionAuth JWT validated")
- let expiry = Date(timeIntervalSince1970: TimeInterval(claims.exp))
- let subscriptionKey = try SymmetricKey(hex: payload.response.subscriptionSymKey)
- let subscriptionTopic = subscriptionKey.rawRepresentation.sha256().toHexString()
- let relay = RelayProtocolOptions(protocol: "irn", data: nil)
- let subscription = PushSubscription(topic: subscriptionTopic, account: payload.request.account, relay: relay, metadata: metadata, scope: [:], expiry: expiry, symKey: subscriptionKey.hexRepresentation)
- try kms.setSymmetricKey(subscriptionKey, for: subscriptionTopic)
- try await networkingInteractor.subscribe(topic: subscriptionTopic)
- return subscription
- }
-
- private func subscribeForProposalErrors() {
- let protocolMethod = NotifyProposeProtocolMethod()
- networkingInteractor.responseErrorSubscription(on: protocolMethod)
- .sink { [unowned self] (payload: ResponseSubscriptionErrorPayload) in
- kms.deletePrivateKey(for: payload.request.publicKey)
- networkingInteractor.unsubscribe(topic: payload.topic)
- guard let error = PushError(code: payload.error.code) else { return }
- proposalResponsePublisherSubject.send(.failure(error))
- }.store(in: &publishers)
- }
-
-}
diff --git a/Sources/WalletConnectPush/Client/Dapp/wc_notifyPropose/NotifyProposer.swift b/Sources/WalletConnectPush/Client/Dapp/wc_notifyPropose/NotifyProposer.swift
deleted file mode 100644
index 02c3720f2..000000000
--- a/Sources/WalletConnectPush/Client/Dapp/wc_notifyPropose/NotifyProposer.swift
+++ /dev/null
@@ -1,33 +0,0 @@
-
-import Foundation
-
-class NotifyProposer {
- private let networkingInteractor: NetworkInteracting
- private let kms: KeyManagementService
- private let logger: ConsoleLogging
- private let metadata: AppMetadata
-
- init(networkingInteractor: NetworkInteracting,
- kms: KeyManagementService,
- appMetadata: AppMetadata,
- logger: ConsoleLogging) {
- self.networkingInteractor = networkingInteractor
- self.kms = kms
- self.metadata = appMetadata
- self.logger = logger
- }
-
- func propose(topic: String, account: Account) async throws {
- logger.debug("NotifyProposer: Sending Notify Proposal")
- let protocolMethod = NotifyProposeProtocolMethod()
- let publicKey = try kms.createX25519KeyPair()
- let responseTopic = publicKey.rawRepresentation.sha256().toHexString()
- try kms.setPublicKey(publicKey: publicKey, for: responseTopic)
-
- let params = NotifyProposeParams(publicKey: publicKey.hexRepresentation, metadata: metadata, account: account, scope: [])
- let request = RPCRequest(method: protocolMethod.method, params: params)
- try await networkingInteractor.request(request, topic: topic, protocolMethod: protocolMethod)
- try await networkingInteractor.subscribe(topic: responseTopic)
- }
-
-}
diff --git a/Sources/WalletConnectPush/Client/Wallet/ProtocolEngine/wc_notifyPropose/NotifyProposeResponder.swift b/Sources/WalletConnectPush/Client/Wallet/ProtocolEngine/wc_notifyPropose/NotifyProposeResponder.swift
deleted file mode 100644
index 01b4fbcf2..000000000
--- a/Sources/WalletConnectPush/Client/Wallet/ProtocolEngine/wc_notifyPropose/NotifyProposeResponder.swift
+++ /dev/null
@@ -1,93 +0,0 @@
-
-import Foundation
-import Combine
-
-class NotifyProposeResponder {
- enum Errors: Error {
- case recordForIdNotFound
- case malformedRequestParams
- case subscriptionNotFound
- }
- private let networkingInteractor: NetworkInteracting
- private let kms: KeyManagementServiceProtocol
- private let logger: ConsoleLogging
- private let pushStorage: PushStorage
- private let pushSubscribeRequester: PushSubscribeRequester
- private let rpcHistory: RPCHistory
-
- private var publishers = [AnyCancellable]()
-
- init(networkingInteractor: NetworkInteracting,
- kms: KeyManagementServiceProtocol,
- logger: ConsoleLogging,
- pushStorage: PushStorage,
- pushSubscribeRequester: PushSubscribeRequester,
- rpcHistory: RPCHistory,
- pushSubscribeResponseSubscriber: PushSubscribeResponseSubscriber
- ) {
- self.networkingInteractor = networkingInteractor
- self.kms = kms
- self.logger = logger
- self.pushStorage = pushStorage
- self.pushSubscribeRequester = pushSubscribeRequester
- self.rpcHistory = rpcHistory
- }
-
- func approve(requestId: RPCID, onSign: @escaping SigningCallback) async throws {
-
- logger.debug("NotifyProposeResponder: approving proposal")
-
- guard let requestRecord = rpcHistory.get(recordId: requestId) else { throw Errors.recordForIdNotFound }
- let proposal = try requestRecord.request.params!.get(NotifyProposeParams.self)
-
- let subscriptionAuthWrapper = try await pushSubscribeRequester.subscribe(metadata: proposal.metadata, account: proposal.account, onSign: onSign)
-
- var pushSubscription: PushSubscription!
- try await withCheckedThrowingContinuation { [unowned self] continuation in
- pushStorage.newSubscriptionPublisher
- .first()
- .sink { value in
- pushSubscription = value
- continuation.resume()
- }.store(in: &publishers)
- }
-
- guard let peerPublicKey = try? AgreementPublicKey(hex: proposal.publicKey) else {
- throw Errors.malformedRequestParams
- }
-
- let responseTopic = peerPublicKey.rawRepresentation.sha256().toHexString()
-
- let keys = try generateAgreementKeys(peerPublicKey: peerPublicKey)
-
- try kms.setSymmetricKey(keys.sharedKey, for: responseTopic)
-
- guard let subscriptionKey = kms.getSymmetricKeyRepresentable(for: pushSubscription.topic)?.toHexString() else { throw Errors.subscriptionNotFound }
-
- let responseParams = NotifyProposeResponseParams(subscriptionAuth: subscriptionAuthWrapper.subscriptionAuth, subscriptionSymKey: subscriptionKey)
-
- let response = RPCResponse(id: requestId, result: responseParams)
-
- let protocolMethod = NotifyProposeProtocolMethod()
-
- logger.debug("NotifyProposeResponder: sending response")
-
- try await networkingInteractor.respond(topic: responseTopic, response: response, protocolMethod: protocolMethod, envelopeType: .type1(pubKey: keys.publicKey.rawRepresentation))
- kms.deleteSymmetricKey(for: responseTopic)
- }
-
- func reject(requestId: RPCID) async throws {
- logger.debug("NotifyProposeResponder - rejecting notify request")
- guard let requestRecord = rpcHistory.get(recordId: requestId) else { throw Errors.recordForIdNotFound }
- let pairingTopic = requestRecord.topic
-
- try await networkingInteractor.respondError(topic: pairingTopic, requestId: requestId, protocolMethod: NotifyProposeProtocolMethod(), reason: PushError.userRejeted)
- }
-
- private func generateAgreementKeys(peerPublicKey: AgreementPublicKey) throws -> AgreementKeys {
- let selfPubKey = try kms.createX25519KeyPair()
- let keys = try kms.performKeyAgreement(selfPublicKey: selfPubKey, peerPublicKey: peerPublicKey.hexRepresentation)
- return keys
- }
-}
-
diff --git a/Sources/WalletConnectPush/Client/Wallet/ProtocolEngine/wc_notifyPropose/NotifyProposeSubscriber.swift b/Sources/WalletConnectPush/Client/Wallet/ProtocolEngine/wc_notifyPropose/NotifyProposeSubscriber.swift
deleted file mode 100644
index 3ea41d805..000000000
--- a/Sources/WalletConnectPush/Client/Wallet/ProtocolEngine/wc_notifyPropose/NotifyProposeSubscriber.swift
+++ /dev/null
@@ -1,53 +0,0 @@
-
-import Foundation
-import Combine
-
-class NotifyProposeSubscriber {
-
- private let requestPublisherSubject = PassthroughSubject()
- private let networkingInteractor: NetworkInteracting
- private let pushStorage: PushStorage
- private var publishers = Set()
- public var requestPublisher: AnyPublisher {
- requestPublisherSubject.eraseToAnyPublisher()
- }
- public let logger: ConsoleLogging
- private let pairingRegisterer: PairingRegisterer
-
- init(networkingInteractor: NetworkInteracting,
- pushStorage: PushStorage,
- publishers: Set = Set(),
- logger: ConsoleLogging,
- pairingRegisterer: PairingRegisterer) {
- self.networkingInteractor = networkingInteractor
- self.pushStorage = pushStorage
- self.publishers = publishers
- self.logger = logger
- self.pairingRegisterer = pairingRegisterer
- setupSubscription()
- }
-
- func setupSubscription() {
- pairingRegisterer.register(method: NotifyProposeProtocolMethod())
- .sink { [unowned self] (payload: RequestSubscriptionPayload) in
- logger.debug("NotifyProposeSubscriber - new notify propose request")
- guard hasNoSubscription(for: payload.request.metadata.url) else {
- Task(priority: .high) { try await respondError(requestId: payload.id, pairingTopic: payload.topic) }
- return
- }
- requestPublisherSubject.send((id: payload.id, account: payload.request.account, metadata: payload.request.metadata))
- }.store(in: &publishers)
- }
-
- func hasNoSubscription(for domain: String) -> Bool {
- pushStorage.getSubscriptions().first { $0.metadata.url == domain } == nil
- }
-
- func respondError(requestId: RPCID, pairingTopic: String) async throws {
- logger.debug("NotifyProposeSubscriber - responding error for notify propose")
-
- let pairingTopic = pairingTopic
-
- try await networkingInteractor.respondError(topic: pairingTopic, requestId: requestId, protocolMethod: NotifyProposeProtocolMethod(), reason: PushError.userHasExistingSubscription)
- }
-}
diff --git a/Sources/WalletConnectPush/Client/Wallet/ProtocolEngine/wc_notifyUpdate/NotifyUpdateRequester.swift b/Sources/WalletConnectPush/Client/Wallet/ProtocolEngine/wc_notifyUpdate/NotifyUpdateRequester.swift
deleted file mode 100644
index aa1c4fffe..000000000
--- a/Sources/WalletConnectPush/Client/Wallet/ProtocolEngine/wc_notifyUpdate/NotifyUpdateRequester.swift
+++ /dev/null
@@ -1,54 +0,0 @@
-import Foundation
-
-protocol NotifyUpdateRequesting {
- func update(topic: String, scope: Set) async throws
-}
-
-class NotifyUpdateRequester: NotifyUpdateRequesting {
- enum Errors: Error {
- case noSubscriptionForGivenTopic
- }
-
- private let keyserverURL: URL
- private let identityClient: IdentityClient
- private let networkingInteractor: NetworkInteracting
- private let logger: ConsoleLogging
- private let pushStorage: PushStorage
-
- init(keyserverURL: URL,
- identityClient: IdentityClient,
- networkingInteractor: NetworkInteracting,
- logger: ConsoleLogging,
- pushStorage: PushStorage
- ) {
- self.keyserverURL = keyserverURL
- self.identityClient = identityClient
- self.networkingInteractor = networkingInteractor
- self.logger = logger
- self.pushStorage = pushStorage
- }
-
- func update(topic: String, scope: Set) async throws {
- logger.debug("NotifyUpdateRequester: updating subscription for topic: \(topic)")
-
- guard let subscription = pushStorage.getSubscription(topic: topic) else { throw Errors.noSubscriptionForGivenTopic }
-
- let request = try createJWTRequest(subscriptionAccount: subscription.account, dappUrl: subscription.metadata.url, scope: scope)
-
- let protocolMethod = NotifyUpdateProtocolMethod()
-
- try await networkingInteractor.request(request, topic: topic, protocolMethod: protocolMethod)
- }
-
- private func createJWTRequest(subscriptionAccount: Account, dappUrl: String, scope: Set) throws -> RPCRequest {
- let protocolMethod = NotifyUpdateProtocolMethod().method
- let scopeClaim = scope.joined(separator: " ")
- let jwtPayload = SubscriptionJWTPayload(keyserver: keyserverURL, subscriptionAccount: subscriptionAccount, dappUrl: dappUrl, scope: scopeClaim)
- let wrapper = try identityClient.signAndCreateWrapper(
- payload: jwtPayload,
- account: subscriptionAccount
- )
- print(wrapper.subscriptionAuth)
- return RPCRequest(method: protocolMethod, params: wrapper)
- }
-}
diff --git a/Sources/WalletConnectPush/Client/Wallet/ProtocolEngine/wc_notifyUpdate/NotifyUpdateResponseSubscriber.swift b/Sources/WalletConnectPush/Client/Wallet/ProtocolEngine/wc_notifyUpdate/NotifyUpdateResponseSubscriber.swift
deleted file mode 100644
index e3b77d8ca..000000000
--- a/Sources/WalletConnectPush/Client/Wallet/ProtocolEngine/wc_notifyUpdate/NotifyUpdateResponseSubscriber.swift
+++ /dev/null
@@ -1,71 +0,0 @@
-
-import Foundation
-import Combine
-
-class NotifyUpdateResponseSubscriber {
- enum Errors: Error {
- case subscriptionDoesNotExist
- }
-
- private let networkingInteractor: NetworkInteracting
- private var publishers = [AnyCancellable]()
- private let logger: ConsoleLogging
- private let pushStorage: PushStorage
- private let subscriptionScopeProvider: SubscriptionScopeProvider
- private var subscriptionPublisherSubject = PassthroughSubject, Never>()
- var updateSubscriptionPublisher: AnyPublisher, Never> {
- return subscriptionPublisherSubject.eraseToAnyPublisher()
- }
-
- init(networkingInteractor: NetworkInteracting,
- logger: ConsoleLogging,
- subscriptionScopeProvider: SubscriptionScopeProvider,
- pushStorage: PushStorage
- ) {
- self.networkingInteractor = networkingInteractor
- self.logger = logger
- self.pushStorage = pushStorage
- self.subscriptionScopeProvider = subscriptionScopeProvider
- subscribeForUpdateResponse()
- }
-
- private func subscribeForUpdateResponse() {
- let protocolMethod = NotifyUpdateProtocolMethod()
- networkingInteractor.responseSubscription(on: protocolMethod)
- .sink {[unowned self] (payload: ResponseSubscriptionPayload) in
- Task(priority: .high) {
- logger.debug("Received Push Update response")
-
- let subscriptionTopic = payload.topic
-
- let (_, claims) = try SubscriptionJWTPayload.decodeAndVerify(from: payload.request)
- let scope = try await buildScope(selected: claims.scp, dappUrl: claims.aud)
-
- guard let oldSubscription = pushStorage.getSubscription(topic: subscriptionTopic) else {
- logger.debug("NotifyUpdateResponseSubscriber Subscription does not exist")
- subscriptionPublisherSubject.send(.failure(Errors.subscriptionDoesNotExist))
- return
- }
- let expiry = Date(timeIntervalSince1970: TimeInterval(claims.exp))
-
- let updatedSubscription = PushSubscription(topic: subscriptionTopic, account: oldSubscription.account, relay: oldSubscription.relay, metadata: oldSubscription.metadata, scope: scope, expiry: expiry, symKey: oldSubscription.symKey)
-
- try await pushStorage.setSubscription(updatedSubscription)
-
- subscriptionPublisherSubject.send(.success(updatedSubscription))
-
- logger.debug("Updated Subscription")
- }
- }.store(in: &publishers)
- }
-
- private func buildScope(selected: String, dappUrl: String) async throws -> [String: ScopeValue] {
- let selectedScope = selected
- .components(separatedBy: " ")
-
- let availableScope = try await subscriptionScopeProvider.getSubscriptionScope(dappUrl: dappUrl)
- return availableScope.reduce(into: [:]) { $0[$1.name] = ScopeValue(description: $1.description, enabled: selectedScope.contains($1.name)) }
- }
-
- // TODO: handle error response
-}
diff --git a/Sources/WalletConnectPush/Client/Wallet/ProtocolEngine/wc_pushMessage/PushMessageSubscriber.swift b/Sources/WalletConnectPush/Client/Wallet/ProtocolEngine/wc_pushMessage/PushMessageSubscriber.swift
deleted file mode 100644
index 897d86f65..000000000
--- a/Sources/WalletConnectPush/Client/Wallet/ProtocolEngine/wc_pushMessage/PushMessageSubscriber.swift
+++ /dev/null
@@ -1,35 +0,0 @@
-import Foundation
-import Combine
-
-class PushMessageSubscriber {
- private let networkingInteractor: NetworkInteracting
- private let pushStorage: PushStorage
- private let logger: ConsoleLogging
- private var publishers = [AnyCancellable]()
- private let pushMessagePublisherSubject = PassthroughSubject()
-
- public var pushMessagePublisher: AnyPublisher {
- pushMessagePublisherSubject.eraseToAnyPublisher()
- }
-
- init(networkingInteractor: NetworkInteracting, pushStorage: PushStorage, logger: ConsoleLogging) {
- self.networkingInteractor = networkingInteractor
- self.pushStorage = pushStorage
- self.logger = logger
- subscribeForPushMessages()
- }
-
- private func subscribeForPushMessages() {
- let protocolMethod = PushMessageProtocolMethod()
- networkingInteractor.requestSubscription(on: protocolMethod)
- .sink { [unowned self] (payload: RequestSubscriptionPayload) in
- logger.debug("Received Push Message")
-
- let record = PushMessageRecord(id: payload.id.string, topic: payload.topic, message: payload.request, publishedAt: payload.publishedAt)
- pushStorage.setMessage(record)
- pushMessagePublisherSubject.send(record)
-
- }.store(in: &publishers)
-
- }
-}
diff --git a/Sources/WalletConnectPush/Client/Wallet/PushStorage.swift b/Sources/WalletConnectPush/Client/Wallet/PushStorage.swift
deleted file mode 100644
index fa04a9297..000000000
--- a/Sources/WalletConnectPush/Client/Wallet/PushStorage.swift
+++ /dev/null
@@ -1,111 +0,0 @@
-import Foundation
-import Combine
-
-protocol PushStoring {
- func getSubscriptions() -> [PushSubscription]
- func getSubscription(topic: String) -> PushSubscription?
- func setSubscription(_ subscription: PushSubscription) async throws
- func deleteSubscription(topic: String) async throws
-}
-
-final class PushStorage: PushStoring {
-
- private var publishers = Set()
-
- private let subscriptionStore: SyncStore
- private let messagesStore: KeyedDatabase
-
- private let newSubscriptionSubject = PassthroughSubject()
- private let deleteSubscriptionSubject = PassthroughSubject()
-
- private let subscriptionStoreDelegate: PushSubscriptionStoreDelegate
-
- var newSubscriptionPublisher: AnyPublisher {
- return newSubscriptionSubject.eraseToAnyPublisher()
- }
-
- var deleteSubscriptionPublisher: AnyPublisher {
- return deleteSubscriptionSubject.eraseToAnyPublisher()
- }
-
- var subscriptionsPublisher: AnyPublisher<[PushSubscription], Never> {
- return subscriptionStore.dataUpdatePublisher
- }
-
- init(
- subscriptionStore: SyncStore,
- messagesStore: KeyedDatabase,
- subscriptionStoreDelegate: PushSubscriptionStoreDelegate
- ) {
- self.subscriptionStore = subscriptionStore
- self.messagesStore = messagesStore
- self.subscriptionStoreDelegate = subscriptionStoreDelegate
- setupSubscriptions()
- }
-
- // MARK: Configuration
-
- func initialize(account: Account) async throws {
- try await subscriptionStore.initialize(for: account)
- }
-
- func setupSubscriptions(account: Account) async throws {
- try subscriptionStore.setupSubscriptions(account: account)
- }
-
- // MARK: Subscriptions
-
- func getSubscriptions() -> [PushSubscription] {
- return subscriptionStore.getAll()
- }
-
- func getSubscription(topic: String) -> PushSubscription? {
- return subscriptionStore.get(for: topic)
- }
-
- func setSubscription(_ subscription: PushSubscription) async throws {
- try await subscriptionStore.set(object: subscription, for: subscription.account)
- newSubscriptionSubject.send(subscription)
- }
-
- func deleteSubscription(topic: String) async throws {
- try await subscriptionStore.delete(id: topic)
- deleteSubscriptionSubject.send(topic)
- }
-
- // MARK: Messages
-
- func getMessages(topic: String) -> [PushMessageRecord] {
- return messagesStore.getAll(for: topic)
- .sorted{$0.publishedAt > $1.publishedAt}
- }
-
- func deleteMessages(topic: String) {
- messagesStore.deleteAll(for: topic)
- }
-
- func deleteMessage(id: String) {
- guard let result = messagesStore.find(id: id) else { return }
- messagesStore.delete(id: id, for: result.key)
- }
-
- func setMessage(_ record: PushMessageRecord) {
- messagesStore.set(element: record, for: record.topic)
- }
-}
-
-private extension PushStorage {
-
- func setupSubscriptions() {
- subscriptionStore.syncUpdatePublisher.sink { [unowned self] (_, _, update) in
- switch update {
- case .set(let subscription):
- subscriptionStoreDelegate.onUpdate(subscription)
- newSubscriptionSubject.send(subscription)
- case .delete(let object):
- subscriptionStoreDelegate.onDelete(object, pushStorage: self)
- deleteSubscriptionSubject.send(object.topic)
- }
- }.store(in: &publishers)
- }
-}
diff --git a/Sources/WalletConnectPush/Client/Wallet/PushSubscriptionStoreDelegate.swift b/Sources/WalletConnectPush/Client/Wallet/PushSubscriptionStoreDelegate.swift
deleted file mode 100644
index 5831e2453..000000000
--- a/Sources/WalletConnectPush/Client/Wallet/PushSubscriptionStoreDelegate.swift
+++ /dev/null
@@ -1,32 +0,0 @@
-import Foundation
-
-final class PushSubscriptionStoreDelegate {
-
- private let networkingInteractor: NetworkInteracting
- private let kms: KeyManagementServiceProtocol
- private let groupKeychainStorage: KeychainStorageProtocol
-
- init(networkingInteractor: NetworkInteracting, kms: KeyManagementServiceProtocol, groupKeychainStorage: KeychainStorageProtocol) {
- self.networkingInteractor = networkingInteractor
- self.kms = kms
- self.groupKeychainStorage = groupKeychainStorage
- }
-
- func onUpdate(_ subscription: PushSubscription) {
- Task(priority: .high) {
- let symmetricKey = try SymmetricKey(hex: subscription.symKey)
- try kms.setSymmetricKey(symmetricKey, for: subscription.topic)
- try groupKeychainStorage.add(symmetricKey, forKey: subscription.topic)
- try await networkingInteractor.subscribe(topic: subscription.topic)
- }
- }
-
- func onDelete(_ subscription: PushSubscription, pushStorage: PushStorage) {
- Task(priority: .high) {
- kms.deleteSymmetricKey(for: subscription.topic)
- try? groupKeychainStorage.delete(key: subscription.topic)
- networkingInteractor.unsubscribe(topic: subscription.topic)
- pushStorage.deleteMessages(topic: subscription.topic)
- }
- }
-}
diff --git a/Sources/WalletConnectPush/Client/Wallet/PushSyncService.swift b/Sources/WalletConnectPush/Client/Wallet/PushSyncService.swift
deleted file mode 100644
index 7b8ab9979..000000000
--- a/Sources/WalletConnectPush/Client/Wallet/PushSyncService.swift
+++ /dev/null
@@ -1,127 +0,0 @@
-import Foundation
-
-final class PushSyncService {
-
- private let syncClient: SyncClient
- private let historyClient: HistoryClient
- private let logger: ConsoleLogging
- private let subscriptionsStore: SyncStore
- private let messagesStore: KeyedDatabase
- private let networkingInteractor: NetworkInteracting
- private let kms: KeyManagementServiceProtocol
- private let coldStartStore: CodableStore
- private let groupKeychainStorage: KeychainStorageProtocol
-
- init(
- syncClient: SyncClient,
- logger: ConsoleLogging,
- historyClient: HistoryClient,
- subscriptionsStore: SyncStore,
- messagesStore: KeyedDatabase,
- networkingInteractor: NetworkInteracting,
- kms: KeyManagementServiceProtocol,
- coldStartStore: CodableStore,
- groupKeychainStorage: KeychainStorageProtocol
- ) {
- self.syncClient = syncClient
- self.logger = logger
- self.historyClient = historyClient
- self.subscriptionsStore = subscriptionsStore
- self.messagesStore = messagesStore
- self.networkingInteractor = networkingInteractor
- self.kms = kms
- self.coldStartStore = coldStartStore
- self.groupKeychainStorage = groupKeychainStorage
- }
-
- func registerSyncIfNeeded(account: Account, onSign: @escaping SigningCallback) async throws {
- guard !syncClient.isRegistered(account: account) else { return }
-
- let result = await onSign(syncClient.getMessage(account: account))
-
- switch result {
- case .signed(let signature):
- try await syncClient.register(account: account, signature: signature)
- logger.debug("Sync pushSubscriptions store registered and initialized")
- case .rejected:
- throw PushError.registerSignatureRejected
- }
- }
-
- func fetchHistoryIfNeeded(account: Account) async throws {
- guard try isColdStart(account: account) else { return }
-
- try await historyClient.register(tags: [
- "5000", // sync_set
- "5002", // sync_delete
- "4002" // push_message
- ])
-
- let syncTopic = try subscriptionsStore.getStoreTopic(account: account)
-
- let updates: [StoreSetDelete] = try await historyClient.getMessages(
- topic: syncTopic,
- count: 200,
- direction: .backward
- )
-
- let inserts: [PushSubscription] = updates.compactMap { update in
- guard let value = update.value else { return nil }
- return try? JSONDecoder().decode(PushSubscription.self, from: Data(value.utf8))
- }
-
- let deletions: [String] = updates.compactMap { update in
- guard update.value == nil else { return nil }
- return update.key
- }
-
- let subscriptions = inserts.filter { !deletions.contains( $0.databaseId ) }
-
- try subscriptionsStore.setInStore(objects: subscriptions, for: account)
-
- for subscription in subscriptions {
- let symmetricKey = try SymmetricKey(hex: subscription.symKey)
- try kms.setSymmetricKey(symmetricKey, for: subscription.topic)
- try groupKeychainStorage.add(symmetricKey, forKey: subscription.topic)
- try await networkingInteractor.subscribe(topic: subscription.topic)
-
- let historyRecords: [HistoryRecord] = try await historyClient.getRecords(
- topic: subscription.topic,
- count: 200,
- direction: .backward
- )
-
- let messageRecords = historyRecords.map { record in
- return PushMessageRecord(
- id: record.id.string,
- topic: subscription.topic,
- message: record.object,
- publishedAt: Date()
- )
- }
-
- messagesStore.set(elements: messageRecords, for: subscription.topic)
- }
-
- coldStartStore.set(Date(), forKey: account.absoluteString)
- }
-}
-
-private extension PushSyncService {
-
- struct StoreSetDelete: Codable, Equatable {
- let key: String
- let value: String?
- }
-
- func isColdStart(account: Account) throws -> Bool {
- guard let lastFetch = try coldStartStore.get(key: account.absoluteString) else {
- return true
- }
- guard let days = Calendar.current.dateComponents([.day], from: lastFetch, to: Date()).day else {
- return true
- }
-
- return days >= 30
- }
-}
diff --git a/Sources/WalletConnectPush/Client/Wallet/WalletPushClient.swift b/Sources/WalletConnectPush/Client/Wallet/WalletPushClient.swift
deleted file mode 100644
index cb90ef867..000000000
--- a/Sources/WalletConnectPush/Client/Wallet/WalletPushClient.swift
+++ /dev/null
@@ -1,141 +0,0 @@
-import Foundation
-import Combine
-
-public class WalletPushClient {
-
- private var publishers = Set()
-
- /// publishes new subscriptions
- public var newSubscriptionPublisher: AnyPublisher {
- return pushStorage.newSubscriptionPublisher
- }
-
- public var subscriptionErrorPublisher: AnyPublisher {
- return pushSubscribeResponseSubscriber.subscriptionErrorPublisher
- }
-
- public var deleteSubscriptionPublisher: AnyPublisher {
- return pushStorage.deleteSubscriptionPublisher
- }
-
- public var subscriptionsPublisher: AnyPublisher<[PushSubscription], Never> {
- return pushStorage.subscriptionsPublisher
- }
-
- public var requestPublisher: AnyPublisher {
- notifyProposeSubscriber.requestPublisher
- }
-
- public var pushMessagePublisher: AnyPublisher {
- pushMessageSubscriber.pushMessagePublisher
- }
-
- public var updateSubscriptionPublisher: AnyPublisher, Never> {
- return notifyUpdateResponseSubscriber.updateSubscriptionPublisher
- }
-
- private let deletePushSubscriptionService: DeletePushSubscriptionService
- private let pushSubscribeRequester: PushSubscribeRequester
-
- public let logger: ConsoleLogging
-
- private let echoClient: EchoClient
- private let pushStorage: PushStorage
- private let pushSyncService: PushSyncService
- private let pushMessageSubscriber: PushMessageSubscriber
- private let resubscribeService: PushResubscribeService
- private let pushSubscribeResponseSubscriber: PushSubscribeResponseSubscriber
- private let deletePushSubscriptionSubscriber: DeletePushSubscriptionSubscriber
- private let notifyUpdateRequester: NotifyUpdateRequester
- private let notifyUpdateResponseSubscriber: NotifyUpdateResponseSubscriber
- private let notifyProposeResponder: NotifyProposeResponder
- private let notifyProposeSubscriber: NotifyProposeSubscriber
- private let subscriptionsAutoUpdater: SubscriptionsAutoUpdater
-
- init(logger: ConsoleLogging,
- kms: KeyManagementServiceProtocol,
- echoClient: EchoClient,
- pushMessageSubscriber: PushMessageSubscriber,
- pushStorage: PushStorage,
- pushSyncService: PushSyncService,
- deletePushSubscriptionService: DeletePushSubscriptionService,
- resubscribeService: PushResubscribeService,
- pushSubscribeRequester: PushSubscribeRequester,
- pushSubscribeResponseSubscriber: PushSubscribeResponseSubscriber,
- deletePushSubscriptionSubscriber: DeletePushSubscriptionSubscriber,
- notifyUpdateRequester: NotifyUpdateRequester,
- notifyUpdateResponseSubscriber: NotifyUpdateResponseSubscriber,
- notifyProposeResponder: NotifyProposeResponder,
- notifyProposeSubscriber: NotifyProposeSubscriber,
- subscriptionsAutoUpdater: SubscriptionsAutoUpdater
- ) {
- self.logger = logger
- self.echoClient = echoClient
- self.pushMessageSubscriber = pushMessageSubscriber
- self.pushStorage = pushStorage
- self.pushSyncService = pushSyncService
- self.deletePushSubscriptionService = deletePushSubscriptionService
- self.resubscribeService = resubscribeService
- self.pushSubscribeRequester = pushSubscribeRequester
- self.pushSubscribeResponseSubscriber = pushSubscribeResponseSubscriber
- self.deletePushSubscriptionSubscriber = deletePushSubscriptionSubscriber
- self.notifyUpdateRequester = notifyUpdateRequester
- self.notifyUpdateResponseSubscriber = notifyUpdateResponseSubscriber
- self.notifyProposeResponder = notifyProposeResponder
- self.notifyProposeSubscriber = notifyProposeSubscriber
- self.subscriptionsAutoUpdater = subscriptionsAutoUpdater
- }
-
- // TODO: Add docs
- public func enableSync(account: Account, onSign: @escaping SigningCallback) async throws {
- try await pushSyncService.registerSyncIfNeeded(account: account, onSign: onSign)
- try await pushStorage.initialize(account: account)
- try await pushStorage.setupSubscriptions(account: account)
- try await pushSyncService.fetchHistoryIfNeeded(account: account)
- }
-
- public func subscribe(metadata: AppMetadata, account: Account, onSign: @escaping SigningCallback) async throws {
- try await pushSubscribeRequester.subscribe(metadata: metadata, account: account, onSign: onSign)
- }
-
- public func approve(id: RPCID, onSign: @escaping SigningCallback) async throws {
- try await notifyProposeResponder.approve(requestId: id, onSign: onSign)
- }
-
- public func reject(id: RPCID) async throws {
- try await notifyProposeResponder.reject(requestId: id)
- }
-
- public func update(topic: String, scope: Set) async throws {
- try await notifyUpdateRequester.update(topic: topic, scope: scope)
- }
-
- public func getActiveSubscriptions() -> [PushSubscription] {
- return pushStorage.getSubscriptions()
- }
-
- public func getMessageHistory(topic: String) -> [PushMessageRecord] {
- pushStorage.getMessages(topic: topic)
- }
-
- public func deleteSubscription(topic: String) async throws {
- try await deletePushSubscriptionService.delete(topic: topic)
- }
-
- public func deletePushMessage(id: String) {
- pushStorage.deleteMessage(id: id)
- }
-
- public func register(deviceToken: Data) async throws {
- try await echoClient.register(deviceToken: deviceToken)
- }
-}
-
-#if targetEnvironment(simulator)
-extension WalletPushClient {
- public func register(deviceToken: String) async throws {
- try await echoClient.register(deviceToken: deviceToken)
- }
-}
-#endif
-
diff --git a/Sources/WalletConnectPush/Client/Wallet/WalletPushClientFactory.swift b/Sources/WalletConnectPush/Client/Wallet/WalletPushClientFactory.swift
deleted file mode 100644
index d95d7cb6d..000000000
--- a/Sources/WalletConnectPush/Client/Wallet/WalletPushClientFactory.swift
+++ /dev/null
@@ -1,88 +0,0 @@
-import Foundation
-
-public struct WalletPushClientFactory {
-
- public static func create(networkInteractor: NetworkInteracting, pairingRegisterer: PairingRegisterer, echoClient: EchoClient, syncClient: SyncClient, historyClient: HistoryClient) -> WalletPushClient {
- let logger = ConsoleLogger(suffix: "🔔",loggingLevel: .debug)
- let keyValueStorage = UserDefaults.standard
- let keyserverURL = URL(string: "https://keys.walletconnect.com")!
- let keychainStorage = KeychainStorage(serviceIdentifier: "com.walletconnect.sdk")
- let groupKeychainService = GroupKeychainStorage(serviceIdentifier: "group.com.walletconnect.sdk")
-
- return WalletPushClientFactory.create(
- keyserverURL: keyserverURL,
- logger: logger,
- keyValueStorage: keyValueStorage,
- keychainStorage: keychainStorage,
- groupKeychainStorage: groupKeychainService,
- networkInteractor: networkInteractor,
- pairingRegisterer: pairingRegisterer,
- echoClient: echoClient,
- syncClient: syncClient,
- historyClient: historyClient
- )
- }
-
- static func create(
- keyserverURL: URL,
- logger: ConsoleLogging,
- keyValueStorage: KeyValueStorage,
- keychainStorage: KeychainStorageProtocol,
- groupKeychainStorage: KeychainStorageProtocol,
- networkInteractor: NetworkInteracting,
- pairingRegisterer: PairingRegisterer,
- echoClient: EchoClient,
- syncClient: SyncClient,
- historyClient: HistoryClient
- ) -> WalletPushClient {
- let kms = KeyManagementService(keychain: keychainStorage)
- let history = RPCHistoryFactory.createForNetwork(keyValueStorage: keyValueStorage)
- let subscriptionStore: SyncStore = SyncStoreFactory.create(name: PushStorageIdntifiers.pushSubscription, syncClient: syncClient, storage: keyValueStorage)
- let subscriptionStoreDelegate = PushSubscriptionStoreDelegate(networkingInteractor: networkInteractor, kms: kms, groupKeychainStorage: groupKeychainStorage)
- let messagesStore = KeyedDatabase(storage: keyValueStorage, identifier: PushStorageIdntifiers.pushMessagesRecords)
- let pushStorage = PushStorage(subscriptionStore: subscriptionStore, messagesStore: messagesStore, subscriptionStoreDelegate: subscriptionStoreDelegate)
- let coldStartStore = CodableStore(defaults: keyValueStorage, identifier: PushStorageIdntifiers.coldStartStore)
- let pushSyncService = PushSyncService(syncClient: syncClient, logger: logger, historyClient: historyClient, subscriptionsStore: subscriptionStore, messagesStore: messagesStore, networkingInteractor: networkInteractor, kms: kms, coldStartStore: coldStartStore, groupKeychainStorage: groupKeychainStorage)
- let identityClient = IdentityClientFactory.create(keyserver: keyserverURL, keychain: keychainStorage, logger: logger)
- let pushMessageSubscriber = PushMessageSubscriber(networkingInteractor: networkInteractor, pushStorage: pushStorage, logger: logger)
- let deletePushSubscriptionService = DeletePushSubscriptionService(networkingInteractor: networkInteractor, kms: kms, logger: logger, pushStorage: pushStorage)
- let resubscribeService = PushResubscribeService(networkInteractor: networkInteractor, pushStorage: pushStorage)
-
- let dappsMetadataStore = CodableStore(defaults: keyValueStorage, identifier: PushStorageIdntifiers.dappsMetadataStore)
- let subscriptionScopeProvider = SubscriptionScopeProvider()
-
- let pushSubscribeRequester = PushSubscribeRequester(keyserverURL: keyserverURL, networkingInteractor: networkInteractor, identityClient: identityClient, logger: logger, kms: kms, subscriptionScopeProvider: subscriptionScopeProvider, dappsMetadataStore: dappsMetadataStore)
-
- let pushSubscribeResponseSubscriber = PushSubscribeResponseSubscriber(networkingInteractor: networkInteractor, kms: kms, logger: logger, groupKeychainStorage: groupKeychainStorage, pushStorage: pushStorage, dappsMetadataStore: dappsMetadataStore, subscriptionScopeProvider: subscriptionScopeProvider)
-
- let notifyUpdateRequester = NotifyUpdateRequester(keyserverURL: keyserverURL, identityClient: identityClient, networkingInteractor: networkInteractor, logger: logger, pushStorage: pushStorage)
-
- let notifyUpdateResponseSubscriber = NotifyUpdateResponseSubscriber(networkingInteractor: networkInteractor, logger: logger, subscriptionScopeProvider: subscriptionScopeProvider, pushStorage: pushStorage)
- let notifyProposeResponder = NotifyProposeResponder(networkingInteractor: networkInteractor, kms: kms, logger: logger, pushStorage: pushStorage, pushSubscribeRequester: pushSubscribeRequester, rpcHistory: history, pushSubscribeResponseSubscriber: pushSubscribeResponseSubscriber)
-
- let notifyProposeSubscriber = NotifyProposeSubscriber(networkingInteractor: networkInteractor, pushStorage: pushStorage, logger: logger, pairingRegisterer: pairingRegisterer)
-
- let deletePushSubscriptionSubscriber = DeletePushSubscriptionSubscriber(networkingInteractor: networkInteractor, kms: kms, logger: logger, pushStorage: pushStorage)
-
- let subscriptionsAutoUpdater = SubscriptionsAutoUpdater(notifyUpdateRequester: notifyUpdateRequester, logger: logger, pushStorage: pushStorage)
-
- return WalletPushClient(
- logger: logger,
- kms: kms,
- echoClient: echoClient,
- pushMessageSubscriber: pushMessageSubscriber,
- pushStorage: pushStorage,
- pushSyncService: pushSyncService,
- deletePushSubscriptionService: deletePushSubscriptionService,
- resubscribeService: resubscribeService,
- pushSubscribeRequester: pushSubscribeRequester,
- pushSubscribeResponseSubscriber: pushSubscribeResponseSubscriber,
- deletePushSubscriptionSubscriber: deletePushSubscriptionSubscriber,
- notifyUpdateRequester: notifyUpdateRequester,
- notifyUpdateResponseSubscriber: notifyUpdateResponseSubscriber,
- notifyProposeResponder: notifyProposeResponder,
- notifyProposeSubscriber: notifyProposeSubscriber,
- subscriptionsAutoUpdater: subscriptionsAutoUpdater
- )
- }
-}
diff --git a/Sources/WalletConnectPush/Client/Wallet/WebDidResolver.swift b/Sources/WalletConnectPush/Client/Wallet/WebDidResolver.swift
deleted file mode 100644
index f7e6617ad..000000000
--- a/Sources/WalletConnectPush/Client/Wallet/WebDidResolver.swift
+++ /dev/null
@@ -1,15 +0,0 @@
-
-import Foundation
-
-class WebDidResolver {
-
- enum Errors: Error {
- case invalidUrl
- }
-
- func resolveDidDoc(domainUrl: String) async throws -> WebDidDoc {
- guard let didDocUrl = URL(string: "\(domainUrl)/.well-known/did.json") else { throw Errors.invalidUrl }
- let (data, _) = try await URLSession.shared.data(from: didDocUrl)
- return try JSONDecoder().decode(WebDidDoc.self, from: data)
- }
-}
diff --git a/Sources/WalletConnectPush/ProtocolMethods/NotifyProposeProtocolMethod.swift b/Sources/WalletConnectPush/ProtocolMethods/NotifyProposeProtocolMethod.swift
deleted file mode 100644
index 027293d19..000000000
--- a/Sources/WalletConnectPush/ProtocolMethods/NotifyProposeProtocolMethod.swift
+++ /dev/null
@@ -1,12 +0,0 @@
-//
-
-import Foundation
-
-struct NotifyProposeProtocolMethod: ProtocolMethod {
- let method: String = "wc_pushPropose"
-
- let requestConfig: RelayConfig = RelayConfig(tag: 4010, prompt: true, ttl: 86400)
-
- let responseConfig: RelayConfig = RelayConfig(tag: 4011, prompt: true, ttl: 86400)
-}
-
diff --git a/Sources/WalletConnectPush/ProtocolMethods/PushSubscribeProtocolMethod.swift b/Sources/WalletConnectPush/ProtocolMethods/PushSubscribeProtocolMethod.swift
deleted file mode 100644
index a101dc0e5..000000000
--- a/Sources/WalletConnectPush/ProtocolMethods/PushSubscribeProtocolMethod.swift
+++ /dev/null
@@ -1,10 +0,0 @@
-
-import Foundation
-
-struct PushSubscribeProtocolMethod: ProtocolMethod {
- let method: String = "wc_pushSubscribe"
-
- let requestConfig: RelayConfig = RelayConfig(tag: 4006, prompt: true, ttl: 86400)
-
- let responseConfig: RelayConfig = RelayConfig(tag: 4007, prompt: true, ttl: 86400)
-}
diff --git a/Sources/WalletConnectPush/Push.swift b/Sources/WalletConnectPush/Push.swift
index 019b64aa6..fe955d08e 100644
--- a/Sources/WalletConnectPush/Push.swift
+++ b/Sources/WalletConnectPush/Push.swift
@@ -1,36 +1,28 @@
import Foundation
public class Push {
-
- public static var dapp: DappPushClient = {
- return DappPushClientFactory.create(
- metadata: Pair.metadata,
- networkInteractor: Networking.interactor,
- syncClient: Sync.instance
- )
- }()
-
- public static var wallet: WalletPushClient = {
+ static public let pushHost = "echo.walletconnect.com"
+ public static var instance: PushClient = {
guard let config = Push.config else {
- fatalError("Error - you must call Push.configure(_:) before accessing the shared wallet instance.")
+ fatalError("Error - you must call Push.configure(_:) before accessing the shared instance.")
}
- Echo.configure(echoHost: config.echoHost, environment: config.environment)
- return WalletPushClientFactory.create(
- networkInteractor: Networking.interactor,
- pairingRegisterer: Pair.registerer,
- echoClient: Echo.instance,
- syncClient: Sync.instance,
- historyClient: History.instance
- )
+
+ return PushClientFactory.create(
+ projectId: Networking.projectId,
+ pushHost: config.pushHost,
+ environment: config.environment)
}()
private static var config: Config?
private init() { }
- /// Wallet's configuration method
- static public func configure(echoHost: String = "echo.walletconnect.com", environment: APNSEnvironment) {
- Push.config = Push.Config(echoHost: echoHost, environment: environment)
+ /// Push instance config method
+ /// - Parameter clientId: https://github.com/WalletConnect/walletconnect-docs/blob/main/docs/specs/clients/core/relay/relay-client-auth.md#overview
+ static public func configure(
+ pushHost: String = pushHost,
+ environment: APNSEnvironment
+ ) {
+ Push.config = Push.Config(pushHost: pushHost, environment: environment)
}
-
}
diff --git a/Sources/WalletConnectPush/PushClient.swift b/Sources/WalletConnectPush/PushClient.swift
new file mode 100644
index 000000000..2eb23aacd
--- /dev/null
+++ b/Sources/WalletConnectPush/PushClient.swift
@@ -0,0 +1,34 @@
+import Foundation
+
+public class PushClient: PushClientProtocol {
+ private let registerService: PushRegisterService
+
+ init(registerService: PushRegisterService) {
+ self.registerService = registerService
+ }
+
+ public func register(deviceToken: Data) async throws {
+ try await registerService.register(deviceToken: deviceToken)
+ }
+
+#if DEBUG
+ public func register(deviceToken: String) async throws {
+ try await registerService.register(deviceToken: deviceToken)
+ }
+#endif
+}
+
+
+#if DEBUG
+final class PushClientMock: PushClientProtocol {
+ var registedCalled = false
+
+ func register(deviceToken: Data) async throws {
+ registedCalled = true
+ }
+
+ func register(deviceToken: String) async throws {
+ registedCalled = true
+ }
+}
+#endif
diff --git a/Sources/WalletConnectPush/PushClientFactory.swift b/Sources/WalletConnectPush/PushClientFactory.swift
new file mode 100644
index 000000000..50a0145f9
--- /dev/null
+++ b/Sources/WalletConnectPush/PushClientFactory.swift
@@ -0,0 +1,39 @@
+import Foundation
+
+public struct PushClientFactory {
+ public static func create(projectId: String,
+ pushHost: String,
+ environment: APNSEnvironment) -> PushClient {
+
+ let keychainStorage = KeychainStorage(serviceIdentifier: "com.walletconnect.sdk")
+
+ return PushClientFactory.create(
+ projectId: projectId,
+ pushHost: pushHost,
+ keychainStorage: keychainStorage,
+ environment: environment)
+ }
+
+ public static func create(
+ projectId: String,
+ pushHost: String,
+ keychainStorage: KeychainStorageProtocol,
+ environment: APNSEnvironment
+ ) -> PushClient {
+ let sessionConfiguration = URLSessionConfiguration.default
+ sessionConfiguration.timeoutIntervalForRequest = 5.0
+ sessionConfiguration.timeoutIntervalForResource = 5.0
+ let session = URLSession(configuration: sessionConfiguration)
+
+ let logger = ConsoleLogger(prefix: "👂🏻", loggingLevel: .off)
+ let httpClient = HTTPNetworkClient(host: pushHost, session: session)
+
+ let clientIdStorage = ClientIdStorage(keychain: keychainStorage)
+
+ let pushAuthenticator = PushAuthenticator(clientIdStorage: clientIdStorage, pushHost: pushHost)
+
+ let registerService = PushRegisterService(httpClient: httpClient, projectId: projectId, clientIdStorage: clientIdStorage, pushAuthenticator: pushAuthenticator, logger: logger, environment: environment)
+
+ return PushClient(registerService: registerService)
+ }
+}
diff --git a/Sources/WalletConnectEcho/EchoClientProtocol.swift b/Sources/WalletConnectPush/PushClientProtocol.swift
similarity index 79%
rename from Sources/WalletConnectEcho/EchoClientProtocol.swift
rename to Sources/WalletConnectPush/PushClientProtocol.swift
index c3b783df9..35eb8f3fa 100644
--- a/Sources/WalletConnectEcho/EchoClientProtocol.swift
+++ b/Sources/WalletConnectPush/PushClientProtocol.swift
@@ -1,6 +1,6 @@
import Foundation
-public protocol EchoClientProtocol {
+public protocol PushClientProtocol {
func register(deviceToken: Data) async throws
#if DEBUG
func register(deviceToken: String) async throws
diff --git a/Sources/WalletConnectPush/PushConfig.swift b/Sources/WalletConnectPush/PushConfig.swift
index 9c955803a..e8d2eab40 100644
--- a/Sources/WalletConnectPush/PushConfig.swift
+++ b/Sources/WalletConnectPush/PushConfig.swift
@@ -2,7 +2,7 @@ import Foundation
extension Push {
struct Config {
- let echoHost: String
+ let pushHost: String
let environment: APNSEnvironment
}
}
diff --git a/Sources/WalletConnectPush/PushImports.swift b/Sources/WalletConnectPush/PushImports.swift
index 5d03d0533..463cb7c23 100644
--- a/Sources/WalletConnectPush/PushImports.swift
+++ b/Sources/WalletConnectPush/PushImports.swift
@@ -1,7 +1,4 @@
#if !CocoaPods
-@_exported import WalletConnectPairing
-@_exported import WalletConnectEcho
-@_exported import WalletConnectIdentity
-@_exported import WalletConnectSync
-@_exported import WalletConnectHistory
+@_exported import WalletConnectNetworking
+@_exported import WalletConnectJWT
#endif
diff --git a/Sources/WalletConnectPush/PushStorageIdntifiers.swift b/Sources/WalletConnectPush/PushStorageIdntifiers.swift
deleted file mode 100644
index 7be76bf7e..000000000
--- a/Sources/WalletConnectPush/PushStorageIdntifiers.swift
+++ /dev/null
@@ -1,9 +0,0 @@
-import Foundation
-
-enum PushStorageIdntifiers {
- static let pushSubscription = "com.walletconnect.notify.pushSubscription"
-
- static let pushMessagesRecords = "com.walletconnect.sdk.pushMessagesRecords"
- static let dappsMetadataStore = "com.walletconnect.sdk.dappsMetadataStore"
- static let coldStartStore = "com.walletconnect.sdk.coldStartStore"
-}
diff --git a/Sources/WalletConnectPush/RPCRequests/PushDeleteParams.swift b/Sources/WalletConnectPush/RPCRequests/PushDeleteParams.swift
deleted file mode 100644
index cc8829319..000000000
--- a/Sources/WalletConnectPush/RPCRequests/PushDeleteParams.swift
+++ /dev/null
@@ -1,10 +0,0 @@
-import Foundation
-
-public struct PushDeleteParams: Codable {
- let code: Int
- let message: String
-
- static var userDisconnected: PushDeleteParams {
- return PushDeleteParams(code: 6000, message: "User Disconnected")
- }
-}
diff --git a/Sources/WalletConnectPush/RPCRequests/SubscriptionJWTPayload.swift b/Sources/WalletConnectPush/RPCRequests/SubscriptionJWTPayload.swift
deleted file mode 100644
index d45ac558b..000000000
--- a/Sources/WalletConnectPush/RPCRequests/SubscriptionJWTPayload.swift
+++ /dev/null
@@ -1,68 +0,0 @@
-import Foundation
-
-struct SubscriptionJWTPayload: JWTClaimsCodable {
-
- struct Claims: JWTClaims {
- /// timestamp when jwt was issued
- let iat: UInt64
- /// timestamp when jwt must expire
- let exp: UInt64
- /// did:key of an identity key. Enables to resolve attached blockchain account.
- let iss: String
- /// key server for identity key verification
- let ksu: String
- /// dapp's url
- let aud: String
- /// blockchain account that push subscription has been proposed for (did:pkh)
- let sub: String
- /// description of action intent. Must be equal to "push_subscription"
- let act: String
-
- let scp: String
- }
-
- struct Wrapper: JWTWrapper {
- let subscriptionAuth: String
-
- init(jwtString: String) {
- self.subscriptionAuth = jwtString
- }
-
- var jwtString: String {
- return subscriptionAuth
- }
- }
-
- let keyserver: URL
- let subscriptionAccount: Account
- let dappUrl: String
- let scope: String
-
- init(keyserver: URL, subscriptionAccount: Account, dappUrl: String, scope: String) {
- self.keyserver = keyserver
- self.subscriptionAccount = subscriptionAccount
- self.dappUrl = dappUrl
- self.scope = scope
- }
-
- init(claims: Claims) throws {
- self.keyserver = try claims.ksu.asURL()
- self.subscriptionAccount = try Account(DIDPKHString: claims.sub)
- self.dappUrl = claims.aud
- self.scope = claims.scp
- }
-
- func encode(iss: String) throws -> Claims {
- return Claims(
- iat: defaultIatMilliseconds(),
- exp: expiry(days: 30),
- iss: iss,
- ksu: keyserver.absoluteString,
- aud: dappUrl,
- sub: subscriptionAccount.did,
- act: "push_subscription",
- scp: scope
- )
- }
-}
-
diff --git a/Sources/WalletConnectEcho/Register/EchoAuthPayload.swift b/Sources/WalletConnectPush/Register/PushAuthPayload.swift
similarity index 80%
rename from Sources/WalletConnectEcho/Register/EchoAuthPayload.swift
rename to Sources/WalletConnectPush/Register/PushAuthPayload.swift
index 12b0e26e9..ef4ecb8a3 100644
--- a/Sources/WalletConnectEcho/Register/EchoAuthPayload.swift
+++ b/Sources/WalletConnectPush/Register/PushAuthPayload.swift
@@ -1,6 +1,6 @@
import Foundation
-struct EchoAuthPayload: JWTClaimsCodable {
+struct PushAuthPayload: JWTClaimsCodable {
struct Claims: JWTClaims {
let iss: String
@@ -8,6 +8,10 @@ struct EchoAuthPayload: JWTClaimsCodable {
let aud: String
let iat: UInt64
let exp: UInt64
+
+ let act: String?
+
+ static var action: String? { nil }
}
struct Wrapper: JWTWrapper {
@@ -33,7 +37,8 @@ struct EchoAuthPayload: JWTClaimsCodable {
sub: subject,
aud: audience,
iat: defaultIat(),
- exp: expiry(days: 1)
+ exp: expiry(days: 1),
+ act: Claims.action
)
}
}
diff --git a/Sources/WalletConnectEcho/Register/EchoAuthenticator.swift b/Sources/WalletConnectPush/Register/PushAuthenticator.swift
similarity index 63%
rename from Sources/WalletConnectEcho/Register/EchoAuthenticator.swift
rename to Sources/WalletConnectPush/Register/PushAuthenticator.swift
index c47247fa4..0091c6bf0 100644
--- a/Sources/WalletConnectEcho/Register/EchoAuthenticator.swift
+++ b/Sources/WalletConnectPush/Register/PushAuthenticator.swift
@@ -1,26 +1,26 @@
import Foundation
-protocol EchoAuthenticating {
+protocol PushAuthenticating {
func createAuthToken() throws -> String
}
-class EchoAuthenticator: EchoAuthenticating {
+class PushAuthenticator: PushAuthenticating {
private let clientIdStorage: ClientIdStoring
- private let echoHost: String
+ private let pushHost: String
- init(clientIdStorage: ClientIdStoring, echoHost: String) {
+ init(clientIdStorage: ClientIdStoring, pushHost: String) {
self.clientIdStorage = clientIdStorage
- self.echoHost = echoHost
+ self.pushHost = pushHost
}
func createAuthToken() throws -> String {
let keyPair = try clientIdStorage.getOrCreateKeyPair()
- let payload = EchoAuthPayload(subject: getSubject(), audience: getAudience())
+ let payload = PushAuthPayload(subject: getSubject(), audience: getAudience())
return try payload.signAndCreateWrapper(keyPair: keyPair).jwtString
}
private func getAudience() -> String {
- return "https://\(echoHost)"
+ return "https://\(pushHost)"
}
private func getSubject() -> String {
diff --git a/Sources/WalletConnectPush/Register/PushRegisterService.swift b/Sources/WalletConnectPush/Register/PushRegisterService.swift
new file mode 100644
index 000000000..2e142ae26
--- /dev/null
+++ b/Sources/WalletConnectPush/Register/PushRegisterService.swift
@@ -0,0 +1,89 @@
+import Foundation
+
+actor PushRegisterService {
+ private let httpClient: HTTPClient
+ private let projectId: String
+ private let logger: ConsoleLogging
+ private let environment: APNSEnvironment
+ private let pushAuthenticator: PushAuthenticating
+ private let clientIdStorage: ClientIdStoring
+ /// The property is used to determine whether echo.walletconnect.org will be used
+ /// in case echo.walletconnect.com doesn't respond for some reason (most likely due to being blocked in the user's location).
+ private var fallback = false
+
+ enum Errors: Error {
+ case registrationFailed
+ }
+
+ init(httpClient: HTTPClient,
+ projectId: String,
+ clientIdStorage: ClientIdStoring,
+ pushAuthenticator: PushAuthenticating,
+ logger: ConsoleLogging,
+ environment: APNSEnvironment) {
+ self.httpClient = httpClient
+ self.clientIdStorage = clientIdStorage
+ self.pushAuthenticator = pushAuthenticator
+ self.projectId = projectId
+ self.logger = logger
+ self.environment = environment
+ }
+
+ func register(deviceToken: Data) async throws {
+ let tokenParts = deviceToken.map { data in String(format: "%02.2hhx", data) }
+ let token = tokenParts.joined()
+ let pushAuthToken = try pushAuthenticator.createAuthToken()
+ let clientId = try clientIdStorage.getClientId()
+ let clientIdMutlibase = try DIDKey(did: clientId).multibase(variant: .ED25519)
+ logger.debug("APNS device token: \(token)")
+
+ do {
+ let response = try await httpClient.request(
+ PushResponse.self,
+ at: PushAPI.register(clientId: clientIdMutlibase, token: token, projectId: projectId, environment: environment, auth: pushAuthToken)
+ )
+ guard response.status == .success else {
+ throw Errors.registrationFailed
+ }
+ logger.debug("Successfully registered at Echo Server")
+ } catch {
+ if (error as? HTTPError) == .couldNotConnect && !fallback {
+ fallback = true
+ await echoHostFallback()
+ try await register(deviceToken: deviceToken)
+ }
+ throw error
+ }
+ }
+
+ func echoHostFallback() async {
+ await httpClient.updateHost(host: "echo.walletconnect.org")
+ }
+
+#if DEBUG
+ public func register(deviceToken: String) async throws {
+ let pushAuthToken = try pushAuthenticator.createAuthToken()
+ let clientId = try clientIdStorage.getClientId()
+ let clientIdMutlibase = try DIDKey(did: clientId).multibase(variant: .ED25519)
+
+ do {
+ let response = try await httpClient.request(
+ PushResponse.self,
+ at: PushAPI.register(clientId: clientIdMutlibase, token: deviceToken, projectId: projectId, environment: environment, auth: pushAuthToken)
+ )
+ guard response.status == .success else {
+ throw Errors.registrationFailed
+ }
+ logger.debug("Successfully registered at Echo Server")
+ } catch {
+ if (error as? HTTPError) == .couldNotConnect && !fallback {
+ fallback = true
+ await echoHostFallback()
+ try await register(deviceToken: deviceToken)
+ }
+ throw error
+ }
+ }
+#endif
+}
+
diff --git a/Sources/WalletConnectEcho/Register/EchoResponse.swift b/Sources/WalletConnectPush/Register/PushResponse.swift
similarity index 82%
rename from Sources/WalletConnectEcho/Register/EchoResponse.swift
rename to Sources/WalletConnectPush/Register/PushResponse.swift
index f480c510c..3dd803c7b 100644
--- a/Sources/WalletConnectEcho/Register/EchoResponse.swift
+++ b/Sources/WalletConnectPush/Register/PushResponse.swift
@@ -1,6 +1,6 @@
import Foundation
-struct EchoResponse: Codable {
+struct PushResponse: Codable {
enum Status: String, Codable {
case success = "SUCCESS"
case failed = "FAILED"
diff --git a/Sources/WalletConnectEcho/Register/EchoService.swift b/Sources/WalletConnectPush/Register/PushService.swift
similarity index 98%
rename from Sources/WalletConnectEcho/Register/EchoService.swift
rename to Sources/WalletConnectPush/Register/PushService.swift
index 44c328101..d6ddbc6f3 100644
--- a/Sources/WalletConnectEcho/Register/EchoService.swift
+++ b/Sources/WalletConnectPush/Register/PushService.swift
@@ -1,6 +1,6 @@
import Foundation
-enum EchoAPI: HTTPService {
+enum PushAPI: HTTPService {
case register(clientId: String, token: String, projectId: String, environment: APNSEnvironment, auth: String)
case unregister(clientId: String, projectId: String, auth: String)
diff --git a/Sources/WalletConnectPush/Types/PushRequest.swift b/Sources/WalletConnectPush/Types/PushRequest.swift
deleted file mode 100644
index d1c4c8a92..000000000
--- a/Sources/WalletConnectPush/Types/PushRequest.swift
+++ /dev/null
@@ -1,3 +0,0 @@
-import Foundation
-
-public typealias PushRequest = (id: RPCID, account: Account, metadata: AppMetadata)
diff --git a/Sources/WalletConnectPush/Types/PushSubscriptionResult.swift b/Sources/WalletConnectPush/Types/PushSubscriptionResult.swift
deleted file mode 100644
index 3e7044917..000000000
--- a/Sources/WalletConnectPush/Types/PushSubscriptionResult.swift
+++ /dev/null
@@ -1,7 +0,0 @@
-
-import Foundation
-
-public struct PushSubscriptionResult: Equatable, Codable {
- public let pushSubscription: PushSubscription
- public let subscriptionAuth: String
-}
diff --git a/Sources/WalletConnectRelay/ClientAuth/RelayAuthPayload.swift b/Sources/WalletConnectRelay/ClientAuth/RelayAuthPayload.swift
index 737f515c0..3c44c4e5c 100644
--- a/Sources/WalletConnectRelay/ClientAuth/RelayAuthPayload.swift
+++ b/Sources/WalletConnectRelay/ClientAuth/RelayAuthPayload.swift
@@ -12,6 +12,10 @@ struct RelayAuthPayload: JWTClaimsCodable {
let aud: String
let iat: UInt64
let exp: UInt64
+
+ let act: String?
+
+ static var action: String? { nil }
}
let subject: String
@@ -33,7 +37,8 @@ struct RelayAuthPayload: JWTClaimsCodable {
sub: subject,
aud: audience,
iat: defaultIat(),
- exp: expiry(days: 1)
+ exp: expiry(days: 1),
+ act: Claims.action
)
}
}
diff --git a/Sources/WalletConnectRelay/Dispatching.swift b/Sources/WalletConnectRelay/Dispatching.swift
index 0b3bb8b56..bfca5d4cf 100644
--- a/Sources/WalletConnectRelay/Dispatching.swift
+++ b/Sources/WalletConnectRelay/Dispatching.swift
@@ -20,6 +20,9 @@ final class Dispatcher: NSObject, Dispatching {
private let logger: ConsoleLogging
private let defaultTimeout: Int = 5
+
+ /// The property is used to determine whether relay.walletconnect.org will be used
+ /// in case relay.walletconnect.com doesn't respond for some reason (most likely due to being blocked in the user's location).
private var fallback = false
private let socketConnectionStatusPublisherSubject = CurrentValueSubject(.disconnected)
@@ -73,10 +76,7 @@ final class Dispatcher: NSObject, Dispatching {
.filter { $0 == .connected }
.setFailureType(to: NetworkError.self)
.timeout(.seconds(defaultTimeout), scheduler: concurrentQueue, customError: { .webSocketNotConnected })
- .sink(receiveCompletion: { [weak self] result in
- guard let self else {
- return
- }
+ .sink(receiveCompletion: { [unowned self] result in
switch result {
case .failure(let error):
cancellable?.cancel()
@@ -141,7 +141,9 @@ extension Dispatcher {
logger.debug("[WebSocket] - Fallback to \(NetworkConstants.fallbackUrl)")
fallback = true
socket.request.url = relayUrlFactory.create(fallback: fallback)
- socket.connect()
+ Task(priority: .high) {
+ await self.socketConnectionHandler.handleDisconnection()
+ }
}
}
}
diff --git a/Sources/WalletConnectRelay/NetworkMonitoring.swift b/Sources/WalletConnectRelay/NetworkMonitoring.swift
index c4200171f..1d3932db5 100644
--- a/Sources/WalletConnectRelay/NetworkMonitoring.swift
+++ b/Sources/WalletConnectRelay/NetworkMonitoring.swift
@@ -1,27 +1,32 @@
import Foundation
+import Combine
import Network
-protocol NetworkMonitoring: AnyObject {
- var onSatisfied: (() -> Void)? {get set}
- var onUnsatisfied: (() -> Void)? {get set}
- func startMonitoring()
+public enum NetworkConnectionStatus {
+ case connected
+ case notConnected
}
-class NetworkMonitor: NetworkMonitoring {
- var onSatisfied: (() -> Void)?
- var onUnsatisfied: (() -> Void)?
-
- private let monitor = NWPathMonitor()
- private let monitorQueue = DispatchQueue(label: "com.walletconnect.sdk.network.monitor")
+public protocol NetworkMonitoring: AnyObject {
+ var networkConnectionStatusPublisher: AnyPublisher { get }
+}
- func startMonitoring() {
- monitor.pathUpdateHandler = { [weak self] path in
- if path.status == .satisfied {
- self?.onSatisfied?()
- } else {
- self?.onUnsatisfied?()
- }
+public final class NetworkMonitor: NetworkMonitoring {
+ private let networkMonitor = NWPathMonitor()
+ private let workerQueue = DispatchQueue(label: "com.walletconnect.sdk.network.monitor")
+
+ private let networkConnectionStatusPublisherSubject = CurrentValueSubject(.connected)
+
+ public var networkConnectionStatusPublisher: AnyPublisher {
+ networkConnectionStatusPublisherSubject
+ .share()
+ .eraseToAnyPublisher()
+ }
+
+ public init() {
+ networkMonitor.pathUpdateHandler = { [weak self] path in
+ self?.networkConnectionStatusPublisherSubject.send((path.status == .satisfied) ? .connected : .notConnected)
}
- monitor.start(queue: monitorQueue)
+ networkMonitor.start(queue: workerQueue)
}
}
diff --git a/Sources/WalletConnectRelay/PackageConfig.json b/Sources/WalletConnectRelay/PackageConfig.json
index e0c9c2edf..b0a94210e 100644
--- a/Sources/WalletConnectRelay/PackageConfig.json
+++ b/Sources/WalletConnectRelay/PackageConfig.json
@@ -1 +1 @@
-{"version": "1.6.14"}
+{"version": "1.7.1"}
diff --git a/Sources/WalletConnectRelay/RelayClient.swift b/Sources/WalletConnectRelay/RelayClient.swift
index ddb9cf91b..441f40314 100644
--- a/Sources/WalletConnectRelay/RelayClient.swift
+++ b/Sources/WalletConnectRelay/RelayClient.swift
@@ -42,6 +42,11 @@ public final class RelayClient {
private let concurrentQueue = DispatchQueue(label: "com.walletconnect.sdk.relay_client", attributes: .concurrent)
+ public var logsPublisher: AnyPublisher {
+ logger.logsPublisher
+ .eraseToAnyPublisher()
+ }
+
// MARK: - Initialization
init(
@@ -63,6 +68,10 @@ public final class RelayClient {
}
}
+ public func setLogging(level: LoggingLevel) {
+ logger.setLogging(level: level)
+ }
+
/// Connects web socket
///
/// Use this method for manual socket connection only
@@ -82,12 +91,12 @@ public final class RelayClient {
let request = Publish(params: .init(topic: topic, message: payload, ttl: ttl, prompt: prompt, tag: tag))
.asRPCRequest()
let message = try request.asJSONEncodedString()
- logger.debug("[RelayClient]: Publishing payload on topic: \(topic)")
+ logger.debug("Publishing payload on topic: \(topic)")
try await dispatcher.protectedSend(message)
}
public func subscribe(topic: String) async throws {
- logger.debug("[RelayClient]: Subscribing to topic: \(topic)")
+ logger.debug("Subscribing to topic: \(topic)")
let rpc = Subscribe(params: .init(topic: topic))
let request = rpc
.asRPCRequest()
@@ -98,7 +107,7 @@ public final class RelayClient {
public func batchSubscribe(topics: [String]) async throws {
guard !topics.isEmpty else { return }
- logger.debug("[RelayClient]: Subscribing to topics: \(topics)")
+ logger.debug("Subscribing to topics: \(topics)")
let rpc = BatchSubscribe(params: .init(topics: topics))
let request = rpc
.asRPCRequest()
@@ -134,7 +143,7 @@ public final class RelayClient {
completion(Errors.subscriptionIdNotFound)
return
}
- logger.debug("[RelayClient]: Unsubscribing from topic: \(topic)")
+ logger.debug("Unsubscribing from topic: \(topic)")
let rpc = Unsubscribe(params: .init(id: subscriptionId, topic: topic))
let request = rpc
.asRPCRequest()
@@ -142,7 +151,7 @@ public final class RelayClient {
rpcHistory.deleteAll(forTopic: topic)
dispatcher.protectedSend(message) { [weak self] error in
if let error = error {
- self?.logger.debug("[RelayClient]:Failed to unsubscribe from topic")
+ self?.logger.debug("Failed to unsubscribe from topic")
completion(error)
} else {
self?.concurrentQueue.async(flags: .barrier) {
@@ -161,9 +170,9 @@ public final class RelayClient {
.sink { [unowned self] (_, subscriptionIds) in
cancellable?.cancel()
concurrentQueue.async(flags: .barrier) { [unowned self] in
- logger.debug("[RelayClient]: Subscribed to topics: \(topics)")
+ logger.debug("Subscribed to topics: \(topics)")
guard topics.count == subscriptionIds.count else {
- logger.warn("RelayClient: Number of topics in (batch)subscribe does not match number of subscriptions")
+ logger.warn("Number of topics in (batch)subscribe does not match number of subscriptions")
return
}
for i in 0.. Void)?
@@ -20,6 +21,7 @@ final class ApproveEngine {
private let sessionStore: WCSessionStorage
private let verifyClient: VerifyClientProtocol
private let proposalPayloadsStore: CodableStore>
+ private let verifyContextStore: CodableStore
private let sessionTopicToProposal: CodableStore
private let pairingRegisterer: PairingRegisterer
private let metadata: AppMetadata
@@ -31,6 +33,7 @@ final class ApproveEngine {
init(
networkingInteractor: NetworkInteracting,
proposalPayloadsStore: CodableStore>,
+ verifyContextStore: CodableStore,
sessionTopicToProposal: CodableStore,
pairingRegisterer: PairingRegisterer,
metadata: AppMetadata,
@@ -42,6 +45,7 @@ final class ApproveEngine {
) {
self.networkingInteractor = networkingInteractor
self.proposalPayloadsStore = proposalPayloadsStore
+ self.verifyContextStore = verifyContextStore
self.sessionTopicToProposal = sessionTopicToProposal
self.pairingRegisterer = pairingRegisterer
self.metadata = metadata
@@ -60,11 +64,17 @@ final class ApproveEngine {
guard let payload = try proposalPayloadsStore.get(key: proposerPubKey) else {
throw Errors.wrongRequestParams
}
+
+ let networkConnectionStatus = await resolveNetworkConnectionStatus()
+ guard networkConnectionStatus == .connected else {
+ throw Errors.networkNotConnected
+ }
let proposal = payload.request
let pairingTopic = payload.topic
proposalPayloadsStore.delete(forKey: proposerPubKey)
+ verifyContextStore.delete(forKey: proposerPubKey)
try Namespace.validate(sessionNamespaces)
try Namespace.validateApproved(sessionNamespaces, against: proposal.requiredNamespaces)
@@ -113,6 +123,7 @@ final class ApproveEngine {
throw Errors.proposalPayloadsNotFound
}
proposalPayloadsStore.delete(forKey: proposerPubKey)
+ verifyContextStore.delete(forKey: proposerPubKey)
try await networkingInteractor.respondError(topic: payload.topic, requestId: payload.id, protocolMethod: SessionProposeProtocolMethod(), reason: reason)
// TODO: Delete pairing if inactive
}
@@ -293,6 +304,13 @@ private extension ApproveEngine {
}
proposalPayloadsStore.set(payload, forKey: proposal.proposer.publicKey)
+ pairingRegisterer.setReceived(pairingTopic: payload.topic)
+
+ if let verifyContext = try? verifyContextStore.get(key: proposal.proposer.publicKey) {
+ onSessionProposal?(proposal.publicRepresentation(pairingTopic: payload.topic), verifyContext)
+ return
+ }
+
Task(priority: .high) {
let assertionId = payload.decryptedPayload.sha256().toHexString()
do {
@@ -301,6 +319,7 @@ private extension ApproveEngine {
origin: origin,
domain: payload.request.proposer.metadata.url
)
+ verifyContextStore.set(verifyContext, forKey: proposal.proposer.publicKey)
onSessionProposal?(proposal.publicRepresentation(pairingTopic: payload.topic), verifyContext)
} catch {
let verifyContext = verifyClient.createVerifyContext(origin: nil, domain: payload.request.proposer.metadata.url)
@@ -365,4 +384,28 @@ private extension ApproveEngine {
}
onSessionSettle?(session.publicRepresentation())
}
+
+ func resolveNetworkConnectionStatus() async -> NetworkConnectionStatus {
+ return await withCheckedContinuation { continuation in
+ let cancellable = networkingInteractor.networkConnectionStatusPublisher.sink { value in
+ continuation.resume(returning: value)
+ }
+
+ Task(priority: .high) {
+ await withTaskCancellationHandler {
+ cancellable.cancel()
+ } onCancel: { }
+ }
+ }
+ }
+}
+
+// MARK: - LocalizedError
+extension ApproveEngine.Errors: LocalizedError {
+ var errorDescription: String? {
+ switch self {
+ case .networkNotConnected: return "Action failed. You seem to be offline"
+ default: return ""
+ }
+ }
}
diff --git a/Sources/WalletConnectSign/Engine/Common/SessionEngine.swift b/Sources/WalletConnectSign/Engine/Common/SessionEngine.swift
index d8f265dab..b90a06fb5 100644
--- a/Sources/WalletConnectSign/Engine/Common/SessionEngine.swift
+++ b/Sources/WalletConnectSign/Engine/Common/SessionEngine.swift
@@ -17,6 +17,7 @@ final class SessionEngine {
private let sessionStore: WCSessionStorage
private let networkingInteractor: NetworkInteracting
private let historyService: HistoryService
+ private let verifyContextStore: CodableStore
private let verifyClient: VerifyClientProtocol
private let kms: KeyManagementServiceProtocol
private var publishers = [AnyCancellable]()
@@ -25,6 +26,7 @@ final class SessionEngine {
init(
networkingInteractor: NetworkInteracting,
historyService: HistoryService,
+ verifyContextStore: CodableStore,
verifyClient: VerifyClientProtocol,
kms: KeyManagementServiceProtocol,
sessionStore: WCSessionStorage,
@@ -32,6 +34,7 @@ final class SessionEngine {
) {
self.networkingInteractor = networkingInteractor
self.historyService = historyService
+ self.verifyContextStore = verifyContextStore
self.verifyClient = verifyClient
self.kms = kms
self.sessionStore = sessionStore
@@ -82,6 +85,7 @@ final class SessionEngine {
protocolMethod: protocolMethod,
reason: SignReasonCode.sessionRequestExpired
)
+ verifyContextStore.delete(forKey: requestId.string)
throw Errors.sessionRequestExpired
}
@@ -90,6 +94,7 @@ final class SessionEngine {
response: RPCResponse(id: requestId, outcome: response),
protocolMethod: protocolMethod
)
+ verifyContextStore.delete(forKey: requestId.string)
}
func emit(topic: String, event: SessionType.EventParams.Event, chainId: Blockchain) async throws {
@@ -185,7 +190,7 @@ private extension SessionEngine {
}
func sessionRequestNotExpired(requestId: RPCID) -> Bool {
- guard let request = historyService.getSessionRequest(id: requestId)
+ guard let request = historyService.getSessionRequest(id: requestId)?.request
else { return false }
return !request.isExpired()
@@ -243,9 +248,11 @@ private extension SessionEngine {
do {
let origin = try await verifyClient.verifyOrigin(assertionId: assertionId)
let verifyContext = verifyClient.createVerifyContext(origin: origin, domain: session.peerParticipant.metadata.url)
+ verifyContextStore.set(verifyContext, forKey: request.id.string)
onSessionRequest?(request, verifyContext)
} catch {
let verifyContext = verifyClient.createVerifyContext(origin: nil, domain: session.peerParticipant.metadata.url)
+ verifyContextStore.set(verifyContext, forKey: request.id.string)
onSessionRequest?(request, verifyContext)
return
}
diff --git a/Sources/WalletConnectSign/Services/HistoryService.swift b/Sources/WalletConnectSign/Services/HistoryService.swift
index 394ff0c61..5a47a81d1 100644
--- a/Sources/WalletConnectSign/Services/HistoryService.swift
+++ b/Sources/WalletConnectSign/Services/HistoryService.swift
@@ -3,29 +3,66 @@ import Foundation
final class HistoryService {
private let history: RPCHistory
+ private let proposalPayloadsStore: CodableStore>
+ private let verifyContextStore: CodableStore
- init(history: RPCHistory) {
+ init(
+ history: RPCHistory,
+ proposalPayloadsStore: CodableStore>,
+ verifyContextStore: CodableStore
+ ) {
self.history = history
+ self.proposalPayloadsStore = proposalPayloadsStore
+ self.verifyContextStore = verifyContextStore
}
- func getPendingRequests() -> [Request] {
- return history.getPending()
+ public func getSessionRequest(id: RPCID) -> (request: Request, context: VerifyContext?)? {
+ guard let record = history.get(recordId: id) else { return nil }
+ guard let request = mapRequestRecord(record) else {
+ return nil
+ }
+ return (request, try? verifyContextStore.get(key: request.id.string))
+ }
+
+ func getPendingRequests() -> [(request: Request, context: VerifyContext?)] {
+ let requests = history.getPending()
.compactMap { mapRequestRecord($0) }
.filter { !$0.isExpired() }
+ return requests.map { ($0, try? verifyContextStore.get(key: $0.id.string)) }
}
- func getPendingRequests(topic: String) -> [Request] {
- return getPendingRequests().filter { $0.topic == topic }
+ func getPendingRequests(topic: String) -> [(request: Request, context: VerifyContext?)] {
+ return getPendingRequests().filter { $0.request.topic == topic }
}
-
- public func getSessionRequest(id: RPCID) -> Request? {
- guard let record = history.get(recordId: id) else { return nil }
- return mapRequestRecord(record)
+
+ func getPendingProposals() -> [(proposal: Session.Proposal, context: VerifyContext?)] {
+ let pendingHistory = history.getPending()
+
+ let requestSubscriptionPayloads = pendingHistory
+ .compactMap { record -> RequestSubscriptionPayload? in
+ guard let proposalParams = mapProposeParams(record) else {
+ return nil
+ }
+ return RequestSubscriptionPayload(id: record.id, topic: record.topic, request: proposalParams, decryptedPayload: Data(), publishedAt: Date(), derivedTopic: nil)
+ }
+
+ requestSubscriptionPayloads.forEach {
+ let proposal = $0.request
+ proposalPayloadsStore.set($0, forKey: proposal.proposer.publicKey)
+ }
+
+ let proposals = pendingHistory
+ .compactMap { mapProposalRecord($0) }
+
+ return proposals.map { ($0, try? verifyContextStore.get(key: $0.proposal.proposer.publicKey)) }
+ }
+
+ func getPendingProposals(topic: String) -> [(proposal: Session.Proposal, context: VerifyContext?)] {
+ return getPendingProposals().filter { $0.proposal.pairingTopic == topic }
}
}
private extension HistoryService {
-
func mapRequestRecord(_ record: RPCHistory.Record) -> Request? {
guard let request = try? record.request.params?.get(SessionType.RequestParams.self)
else { return nil }
@@ -39,4 +76,25 @@ private extension HistoryService {
expiry: request.request.expiry
)
}
+
+ func mapProposeParams(_ record: RPCHistory.Record) -> SessionType.ProposeParams? {
+ guard let proposal = try? record.request.params?.get(SessionType.ProposeParams.self)
+ else { return nil }
+ return proposal
+ }
+
+ func mapProposalRecord(_ record: RPCHistory.Record) -> Session.Proposal? {
+ guard let proposal = try? record.request.params?.get(SessionType.ProposeParams.self)
+ else { return nil }
+
+ return Session.Proposal(
+ id: proposal.proposer.publicKey,
+ pairingTopic: record.topic,
+ proposer: proposal.proposer.metadata,
+ requiredNamespaces: proposal.requiredNamespaces,
+ optionalNamespaces: proposal.optionalNamespaces,
+ sessionProperties: proposal.sessionProperties,
+ proposal: proposal
+ )
+ }
}
diff --git a/Sources/WalletConnectSign/Session.swift b/Sources/WalletConnectSign/Session.swift
index 0d6ca96a8..7e0d24fb4 100644
--- a/Sources/WalletConnectSign/Session.swift
+++ b/Sources/WalletConnectSign/Session.swift
@@ -3,7 +3,7 @@ import Foundation
/**
A representation of an active session connection.
*/
-public struct Session {
+public struct Session: Codable {
public let topic: String
public let pairingTopic: String
public let peer: AppMetadata
@@ -28,6 +28,24 @@ extension Session {
// TODO: Refactor internal objects to manage only needed data
internal let proposal: SessionProposal
+
+ init(
+ id: String,
+ pairingTopic: String,
+ proposer: AppMetadata,
+ requiredNamespaces: [String: ProposalNamespace],
+ optionalNamespaces: [String: ProposalNamespace]?,
+ sessionProperties: [String: String]?,
+ proposal: SessionProposal
+ ) {
+ self.id = id
+ self.pairingTopic = pairingTopic
+ self.proposer = proposer
+ self.requiredNamespaces = requiredNamespaces
+ self.optionalNamespaces = optionalNamespaces
+ self.sessionProperties = sessionProperties
+ self.proposal = proposal
+ }
}
public struct Event: Equatable, Hashable {
diff --git a/Sources/WalletConnectSign/Sign/SignClient.swift b/Sources/WalletConnectSign/Sign/SignClient.swift
index 11830442c..57dd897a2 100644
--- a/Sources/WalletConnectSign/Sign/SignClient.swift
+++ b/Sources/WalletConnectSign/Sign/SignClient.swift
@@ -338,17 +338,27 @@ public final class SignClient: SignClientProtocol {
/// Query pending requests
/// - Returns: Pending requests received from peer with `wc_sessionRequest` protocol method
/// - Parameter topic: topic representing session for which you want to get pending requests. If nil, you will receive pending requests for all active sessions.
- public func getPendingRequests(topic: String? = nil) -> [Request] {
+ public func getPendingRequests(topic: String? = nil) -> [(request: Request, context: VerifyContext?)] {
if let topic = topic {
return historyService.getPendingRequests(topic: topic)
} else {
return historyService.getPendingRequests()
}
}
+
+ /// Query pending proposals
+ /// - Returns: Pending proposals received from peer with `wc_sessionPropose` protocol method
+ public func getPendingProposals(topic: String? = nil) -> [(proposal: Session.Proposal, context: VerifyContext?)] {
+ if let topic = topic {
+ return historyService.getPendingProposals(topic: topic)
+ } else {
+ return historyService.getPendingProposals()
+ }
+ }
/// - Parameter id: id of a wc_sessionRequest jsonrpc request
/// - Returns: json rpc record object for given id or nil if record for give id does not exits
- public func getSessionRequestRecord(id: RPCID) -> Request? {
+ public func getSessionRequestRecord(id: RPCID) -> (request: Request, context: VerifyContext?)? {
return historyService.getSessionRequest(id: id)
}
diff --git a/Sources/WalletConnectSign/Sign/SignClientFactory.swift b/Sources/WalletConnectSign/Sign/SignClientFactory.swift
index 53a7d56af..a7903a01b 100644
--- a/Sources/WalletConnectSign/Sign/SignClientFactory.swift
+++ b/Sources/WalletConnectSign/Sign/SignClientFactory.swift
@@ -24,13 +24,26 @@ public struct SignClientFactory {
let pairingStore = PairingStorage(storage: SequenceStore(store: .init(defaults: keyValueStorage, identifier: SignStorageIdentifiers.pairings.rawValue)))
let sessionStore = SessionStorage(storage: SequenceStore(store: .init(defaults: keyValueStorage, identifier: SignStorageIdentifiers.sessions.rawValue)))
let proposalPayloadsStore = CodableStore>(defaults: RuntimeKeyValueStorage(), identifier: SignStorageIdentifiers.proposals.rawValue)
- let historyService = HistoryService(history: rpcHistory)
+ let verifyContextStore = CodableStore(defaults: keyValueStorage, identifier: VerifyStorageIdentifiers.context.rawValue)
+ let historyService = HistoryService(history: rpcHistory, proposalPayloadsStore: proposalPayloadsStore, verifyContextStore: verifyContextStore)
let verifyClient = VerifyClientFactory.create()
- let sessionEngine = SessionEngine(networkingInteractor: networkingClient, historyService: historyService, verifyClient: verifyClient, kms: kms, sessionStore: sessionStore, logger: logger)
+ let sessionEngine = SessionEngine(networkingInteractor: networkingClient, historyService: historyService, verifyContextStore: verifyContextStore, verifyClient: verifyClient, kms: kms, sessionStore: sessionStore, logger: logger)
let nonControllerSessionStateMachine = NonControllerSessionStateMachine(networkingInteractor: networkingClient, kms: kms, sessionStore: sessionStore, logger: logger)
let controllerSessionStateMachine = ControllerSessionStateMachine(networkingInteractor: networkingClient, kms: kms, sessionStore: sessionStore, logger: logger)
let sessionTopicToProposal = CodableStore(defaults: RuntimeKeyValueStorage(), identifier: SignStorageIdentifiers.sessionTopicToProposal.rawValue)
- let approveEngine = ApproveEngine(networkingInteractor: networkingClient, proposalPayloadsStore: proposalPayloadsStore, sessionTopicToProposal: sessionTopicToProposal, pairingRegisterer: pairingClient, metadata: metadata, kms: kms, logger: logger, pairingStore: pairingStore, sessionStore: sessionStore, verifyClient: verifyClient)
+ let approveEngine = ApproveEngine(
+ networkingInteractor: networkingClient,
+ proposalPayloadsStore: proposalPayloadsStore,
+ verifyContextStore: verifyContextStore,
+ sessionTopicToProposal: sessionTopicToProposal,
+ pairingRegisterer: pairingClient,
+ metadata: metadata,
+ kms: kms,
+ logger: logger,
+ pairingStore: pairingStore,
+ sessionStore: sessionStore,
+ verifyClient: verifyClient
+ )
let cleanupService = SignCleanupService(pairingStore: pairingStore, sessionStore: sessionStore, kms: kms, sessionTopicToProposal: sessionTopicToProposal, networkInteractor: networkingClient)
let deleteSessionService = DeleteSessionService(networkingInteractor: networkingClient, kms: kms, sessionStore: sessionStore, logger: logger)
let disconnectService = DisconnectService(deleteSessionService: deleteSessionService, sessionStorage: sessionStore)
diff --git a/Sources/WalletConnectSign/Sign/SignClientProtocol.swift b/Sources/WalletConnectSign/Sign/SignClientProtocol.swift
index f23857f04..437c115c6 100644
--- a/Sources/WalletConnectSign/Sign/SignClientProtocol.swift
+++ b/Sources/WalletConnectSign/Sign/SignClientProtocol.swift
@@ -24,6 +24,7 @@ public protocol SignClientProtocol {
func getSessions() -> [Session]
func cleanup() async throws
- func getPendingRequests(topic: String?) -> [Request]
- func getSessionRequestRecord(id: RPCID) -> Request?
+ func getPendingRequests(topic: String?) -> [(request: Request, context: VerifyContext?)]
+ func getPendingProposals(topic: String?) -> [(proposal: Session.Proposal, context: VerifyContext?)]
+ func getSessionRequestRecord(id: RPCID) -> (request: Request, context: VerifyContext?)?
}
diff --git a/Sources/WalletConnectSync/Services/SyncService.swift b/Sources/WalletConnectSync/Services/SyncService.swift
index d18a5580a..9902578de 100644
--- a/Sources/WalletConnectSync/Services/SyncService.swift
+++ b/Sources/WalletConnectSync/Services/SyncService.swift
@@ -31,6 +31,22 @@ final class SyncService {
setupSubscriptions()
}
+ func create(account: Account, store: String) async throws {
+ if let _ = try? indexStore.getRecord(account: account, name: store) {
+ return
+ }
+
+ let topic = try derivationService.deriveTopic(account: account, store: store)
+ indexStore.set(topic: topic, name: store, account: account)
+ }
+
+ func subscribe(account: Account, store: String) async throws {
+ guard let record = try? indexStore.getRecord(account: account, name: store) else {
+ throw Errors.recordNotFoundForAccount
+ }
+ try await networkInteractor.subscribe(topic: record.topic)
+ }
+
func set(account: Account, store: String, object: Object) async throws {
let protocolMethod = SyncSetMethod()
let params = StoreSet(key: object.databaseId, value: try object.json())
@@ -57,11 +73,6 @@ final class SyncService {
logger.debug("Did delete value for \(store). Sent on: \(record.topic). Key: \n\(key)\n")
}
-
- func create(account: Account, store: String) async throws {
- let topic = try getTopic(for: account, store: store)
- try await networkInteractor.subscribe(topic: topic)
- }
}
private extension SyncService {
@@ -87,14 +98,4 @@ private extension SyncService {
}
.store(in: &publishers)
}
-
- func getTopic(for account: Account, store: String) throws -> String {
- if let record = try? indexStore.getRecord(account: account, name: store) {
- return record.topic
- }
-
- let topic = try derivationService.deriveTopic(account: account, store: store)
- indexStore.set(topic: topic, name: store, account: account)
- return topic
- }
}
diff --git a/Sources/WalletConnectSync/Stores/SyncStore.swift b/Sources/WalletConnectSync/Stores/SyncStore.swift
index fae99c935..e6d93f32c 100644
--- a/Sources/WalletConnectSync/Stores/SyncStore.swift
+++ b/Sources/WalletConnectSync/Stores/SyncStore.swift
@@ -4,6 +4,7 @@ import Combine
public enum SyncUpdate {
case set(object: Object)
case delete(object: Object)
+ case update(object: Object)
}
public final class SyncStore {
@@ -39,11 +40,15 @@ public final class SyncStore {
setupSubscriptions()
}
- public func initialize(for account: Account) async throws {
+ public func create(for account: Account) async throws {
try await syncClient.create(account: account, store: name)
}
- public func setupSubscriptions(account: Account) throws {
+ public func subscribe(for account: Account) async throws {
+ try await syncClient.subscribe(account: account, store: name)
+ }
+
+ public func setupDatabaseSubscriptions(account: Account) throws {
let record = try indexStore.getRecord(account: account, name: name)
objectStore.onUpdate = { [unowned self] in
@@ -93,8 +98,9 @@ public final class SyncStore {
return record.topic
}
- public func setInStore(objects: [Object], for account: Account) throws {
+ public func replaceInStore(objects: [Object], for account: Account) throws {
let record = try indexStore.getRecord(account: account, name: name)
+ objectStore.deleteAll(for: record.topic)
objectStore.set(elements: objects, for: record.topic)
}
}
@@ -111,8 +117,10 @@ private extension SyncStore {
switch update {
case .set(let set):
let object = try! JSONDecoder().decode(Object.self, from: Data(set.value.utf8))
+ let exists = objectStore.exists(for: record.topic, id: object.databaseId)
if try! setInStore(object: object, for: record.account) {
- syncUpdateSubject.send((topic, record.account, .set(object: object)))
+ let update: SyncUpdate = exists ? .update(object: object) : .set(object: object)
+ syncUpdateSubject.send((topic, record.account, update))
}
case .delete(let delete):
if let object = get(for: delete.key), try! deleteInStore(id: delete.key, for: record.account) {
diff --git a/Sources/WalletConnectSync/SyncClient.swift b/Sources/WalletConnectSync/SyncClient.swift
index fbe09bf6f..e7e336d8e 100644
--- a/Sources/WalletConnectSync/SyncClient.swift
+++ b/Sources/WalletConnectSync/SyncClient.swift
@@ -40,6 +40,11 @@ public final class SyncClient {
try await syncService.create(account: account, store: store)
}
+ /// Subscribe for sync topic
+ public func subscribe(account: Account, store: String) async throws {
+ try await syncService.subscribe(account: account, store: store)
+ }
+
// Set value to store
public func set(
account: Account,
diff --git a/Sources/WalletConnectUtils/Cacao/IATProvider.swift b/Sources/WalletConnectUtils/Cacao/IATProvider.swift
index 22aefa45d..17637c22c 100644
--- a/Sources/WalletConnectUtils/Cacao/IATProvider.swift
+++ b/Sources/WalletConnectUtils/Cacao/IATProvider.swift
@@ -12,3 +12,11 @@ public struct DefaultIATProvider: IATProvider {
return ISO8601DateFormatter().string(from: Date())
}
}
+
+#if DEBUG
+struct IATProviderMock: IATProvider {
+ var iat: String {
+ return "2022-10-10T23:03:35.700Z"
+ }
+}
+#endif
diff --git a/Sources/WalletConnectUtils/KeyedDatabase.swift b/Sources/WalletConnectUtils/KeyedDatabase.swift
index a7807aecd..c619d62d5 100644
--- a/Sources/WalletConnectUtils/KeyedDatabase.swift
+++ b/Sources/WalletConnectUtils/KeyedDatabase.swift
@@ -51,6 +51,11 @@ public class KeyedDatabase where Element: DatabaseObject {
return (value.key, element)
}
+ public func exists(for key: String, id: String) -> Bool {
+ let element = getElement(for: key, id: id)
+ return element != nil
+ }
+
@discardableResult
public func set(elements: [Element], for key: String) -> Bool {
var map = index[key] ?? [:]
@@ -71,7 +76,7 @@ public class KeyedDatabase where Element: DatabaseObject {
var map = index[key] ?? [:]
guard
- map[element.databaseId] == nil else { return false }
+ map[element.databaseId] == nil || map[element.databaseId] != element else { return false }
map[element.databaseId] = element
index[key] = map
@@ -94,8 +99,6 @@ public class KeyedDatabase where Element: DatabaseObject {
@discardableResult
public func deleteAll(for key: String) -> Bool {
- var map = index[key]
-
guard index[key] != nil else { return false }
index[key] = nil
diff --git a/Sources/WalletConnectUtils/Logger.swift b/Sources/WalletConnectUtils/Logger.swift
deleted file mode 100644
index ee6b0aad2..000000000
--- a/Sources/WalletConnectUtils/Logger.swift
+++ /dev/null
@@ -1,80 +0,0 @@
-import Foundation
-
-/// Logging Protocol
-public protocol ConsoleLogging {
- /// Writes a debug message to the log.
- func debug(_ items: Any...)
-
- /// Writes an informative message to the log.
- func info(_ items: Any...)
-
- /// Writes information about a warning to the log.
- func warn(_ items: Any...)
-
- /// Writes information about an error to the log.
- func error(_ items: Any...)
-
- func setLogging(level: LoggingLevel)
-}
-
-public class ConsoleLogger: ConsoleLogging {
- private var loggingLevel: LoggingLevel
- private var suffix: String
-
- public func setLogging(level: LoggingLevel) {
- self.loggingLevel = level
- }
-
- public init(suffix: String? = nil, loggingLevel: LoggingLevel = .warn) {
- self.suffix = suffix ?? ""
- self.loggingLevel = loggingLevel
- }
-
- public func debug(_ items: Any...) {
- if loggingLevel >= .debug {
- items.forEach {
- Swift.print("\(suffix) \($0) - \(logFormattedDate(Date()))")
- }
- }
- }
-
- public func info(_ items: Any...) {
- if loggingLevel >= .info {
- items.forEach {
- Swift.print("\(suffix) \($0)")
- }
- }
- }
-
- public func warn(_ items: Any...) {
- if loggingLevel >= .warn {
- items.forEach {
- Swift.print("\(suffix) ⚠️ \($0)")
- }
- }
- }
-
- public func error(_ items: Any...) {
- if loggingLevel >= .error {
- items.forEach {
- Swift.print("\(suffix) ‼️ \($0)")
- }
- }
- }
-}
-
-public enum LoggingLevel: Comparable {
- case off
- case error
- case warn
- case info
- case debug
-}
-
-
-fileprivate func logFormattedDate(_ date: Date) -> String {
- let dateFormatter = DateFormatter()
- dateFormatter.locale = NSLocale.current
- dateFormatter.dateFormat = "HH:mm:ss.SSSS"
- return dateFormatter.string(from: date)
-}
diff --git a/Sources/WalletConnectUtils/Logger/ConsoleLogger.swift b/Sources/WalletConnectUtils/Logger/ConsoleLogger.swift
new file mode 100644
index 000000000..f87f2dfef
--- /dev/null
+++ b/Sources/WalletConnectUtils/Logger/ConsoleLogger.swift
@@ -0,0 +1,126 @@
+import Foundation
+import Combine
+
+public protocol ConsoleLogging {
+ var logsPublisher: AnyPublisher { get }
+ func debug(_ items: Any..., file: String, function: String, line: Int, properties: [String: String]?)
+ func info(_ items: Any..., file: String, function: String, line: Int)
+ func warn(_ items: Any..., file: String, function: String, line: Int)
+ func error(_ items: Any..., file: String, function: String, line: Int)
+ func setLogging(level: LoggingLevel)
+}
+
+public extension ConsoleLogging {
+ func debug(_ items: Any..., file: String = #file, function: String = #function, line: Int = #line, properties: [String: String]? = nil) {
+ debug(items, file: file, function: function, line: line, properties: properties)
+ }
+ func info(_ items: Any..., file: String = #file, function: String = #function, line: Int = #line) {
+ info(items, file: file, function: function, line: line)
+ }
+ func warn(_ items: Any..., file: String = #file, function: String = #function, line: Int = #line) {
+ warn(items, file: file, function: function, line: line)
+ }
+ func error(_ items: Any..., file: String = #file, function: String = #function, line: Int = #line) {
+ error(items, file: file, function: function, line: line)
+ }
+}
+
+public class ConsoleLogger {
+ private var loggingLevel: LoggingLevel
+ private var prefix: String
+ private var logsPublisherSubject = PassthroughSubject()
+ public var logsPublisher: AnyPublisher {
+ return logsPublisherSubject.eraseToAnyPublisher()
+ }
+
+ public func setLogging(level: LoggingLevel) {
+ self.loggingLevel = level
+ }
+
+ public init(prefix: String? = nil, loggingLevel: LoggingLevel = .warn) {
+ self.prefix = prefix ?? ""
+ self.loggingLevel = loggingLevel
+ }
+
+ private func logMessage(_ items: Any..., logType: LoggingLevel, file: String = #file, function: String = #function, line: Int = #line, properties: [String: String]? = nil) {
+ let fileName = (file as NSString).lastPathComponent
+ items.forEach {
+ var logMessage = "\($0)"
+ var properties = properties ?? [String: String]()
+ properties["fileName"] = fileName
+ properties["line"] = "\(line)"
+ properties["function"] = function
+ switch logType {
+ case .debug:
+ logMessage = "\(prefix) \(logMessage)"
+ logsPublisherSubject.send(.debug(LogMessage(message: logMessage, properties: properties)))
+ case .info:
+ logMessage = "\(prefix) ℹ️ \(logMessage)"
+ logsPublisherSubject.send(.info(LogMessage(message: logMessage, properties: properties)))
+ case .warn:
+ logMessage = "\(prefix) ⚠️ \(logMessage)"
+ logsPublisherSubject.send(.warn(LogMessage(message: logMessage, properties: properties)))
+ case .error:
+ logMessage = "\(prefix) ‼️ \(logMessage)"
+ logsPublisherSubject.send(.error(LogMessage(message: logMessage, properties: properties)))
+ case .off:
+ return
+ }
+ logMessage = "\(prefix) [\(fileName)]: \($0) - \(function) - line: \(line) - \(logFormattedDate(Date()))"
+ Swift.print(logMessage)
+ }
+ }
+
+ private func logFormattedDate(_ date: Date) -> String {
+ let formatter = DateFormatter()
+ formatter.dateFormat = "yyyy-MM-dd HH:mm:ss"
+ return formatter.string(from: date)
+ }
+}
+
+
+extension ConsoleLogger: ConsoleLogging {
+ public func debug(_ items: Any..., file: String, function: String, line: Int, properties: [String : String]?) {
+ if loggingLevel >= .debug {
+ logMessage(items, logType: .debug, file: file, function: function, line: line, properties: properties)
+ }
+ }
+
+ public func info(_ items: Any..., file: String, function: String, line: Int) {
+ if loggingLevel >= .info {
+ logMessage(items, logType: .info, file: file, function: function, line: line)
+ }
+ }
+
+ public func warn(_ items: Any..., file: String, function: String, line: Int) {
+ if loggingLevel >= .warn {
+ logMessage(items, logType: .warn, file: file, function: function, line: line)
+ }
+ }
+
+ public func error(_ items: Any..., file: String, function: String, line: Int) {
+ if loggingLevel >= .error {
+ logMessage(items, logType: .error, file: file, function: function, line: line)
+ }
+ }
+
+}
+
+
+#if DEBUG
+public struct ConsoleLoggerMock: ConsoleLogging {
+ public var logsPublisher: AnyPublisher {
+ return PassthroughSubject().eraseToAnyPublisher()
+ }
+
+ public init() {}
+
+ public func debug(_ items: Any..., file: String, function: String, line: Int, properties: [String: String]?) { }
+ public func info(_ items: Any..., file: String, function: String, line: Int) { }
+ public func warn(_ items: Any..., file: String, function: String, line: Int) { }
+ public func error(_ items: Any..., file: String, function: String, line: Int) { }
+
+ public func setLogging(level: LoggingLevel) { }
+}
+#endif
+
diff --git a/Sources/WalletConnectUtils/Logger/Log.swift b/Sources/WalletConnectUtils/Logger/Log.swift
new file mode 100644
index 000000000..5e3c7f785
--- /dev/null
+++ b/Sources/WalletConnectUtils/Logger/Log.swift
@@ -0,0 +1,26 @@
+import Foundation
+
+public struct LogMessage {
+ public let message: String
+ public let properties: [String: String]?
+ public var aggregated: String {
+ var aggregatedProperties = ""
+
+ properties?.forEach { key, value in
+ aggregatedProperties += "\(key): \(value), "
+ }
+
+ if !aggregatedProperties.isEmpty {
+ aggregatedProperties = String(aggregatedProperties.dropLast(2))
+ }
+
+ return "\(message), properties: [\(aggregatedProperties)]"
+ }
+}
+
+public enum Log {
+ case error(LogMessage)
+ case warn(LogMessage)
+ case info(LogMessage)
+ case debug(LogMessage)
+}
diff --git a/Sources/WalletConnectUtils/Logger/LoggingLevel.swift b/Sources/WalletConnectUtils/Logger/LoggingLevel.swift
new file mode 100644
index 000000000..fbb1f7ec0
--- /dev/null
+++ b/Sources/WalletConnectUtils/Logger/LoggingLevel.swift
@@ -0,0 +1,9 @@
+import Foundation
+
+public enum LoggingLevel: Comparable {
+ case off
+ case error
+ case warn
+ case info
+ case debug
+}
diff --git a/Sources/WalletConnectVerify/OriginVerifier.swift b/Sources/WalletConnectVerify/OriginVerifier.swift
index 039cccecf..2ab3267ae 100644
--- a/Sources/WalletConnectVerify/OriginVerifier.swift
+++ b/Sources/WalletConnectVerify/OriginVerifier.swift
@@ -5,22 +5,44 @@ public final class OriginVerifier {
case registrationFailed
}
- private let verifyHost: String
+ private var verifyHost: String
+ /// The property is used to determine whether verify.walletconnect.org will be used
+ /// in case verify.walletconnect.com doesn't respond for some reason (most likely due to being blocked in the user's location).
+ private var fallback = false
init(verifyHost: String) {
self.verifyHost = verifyHost
}
func verifyOrigin(assertionId: String) async throws -> String {
- let httpClient = HTTPNetworkClient(host: verifyHost)
- let response = try await httpClient.request(
- VerifyResponse.self,
- at: VerifyAPI.resolve(assertionId: assertionId)
- )
- guard let origin = response.origin else {
- throw Errors.registrationFailed
+ let sessionConfiguration = URLSessionConfiguration.default
+ sessionConfiguration.timeoutIntervalForRequest = 5.0
+ sessionConfiguration.timeoutIntervalForResource = 5.0
+ let session = URLSession(configuration: sessionConfiguration)
+
+ let httpClient = HTTPNetworkClient(host: verifyHost, session: session)
+
+ do {
+ let response = try await httpClient.request(
+ VerifyResponse.self,
+ at: VerifyAPI.resolve(assertionId: assertionId)
+ )
+ guard let origin = response.origin else {
+ throw Errors.registrationFailed
+ }
+ return origin
+ } catch {
+ if (error as? HTTPError) == .couldNotConnect && !fallback {
+ fallback = true
+ verifyHostFallback()
+ return try await verifyOrigin(assertionId: assertionId)
+ }
+ throw error
}
- return origin
+ }
+
+ func verifyHostFallback() {
+ verifyHost = "verify.walletconnect.org"
}
}
diff --git a/Sources/WalletConnectVerify/Storage/VerifyStorageIdentifiers.swift b/Sources/WalletConnectVerify/Storage/VerifyStorageIdentifiers.swift
new file mode 100644
index 000000000..32d29d5a5
--- /dev/null
+++ b/Sources/WalletConnectVerify/Storage/VerifyStorageIdentifiers.swift
@@ -0,0 +1,5 @@
+import Foundation
+
+public enum VerifyStorageIdentifiers: String {
+ case context = "com.walletconnect.sdk.verifyContext"
+}
diff --git a/Sources/WalletConnectVerify/VerifyClient.swift b/Sources/WalletConnectVerify/VerifyClient.swift
index 0b67e0f70..b1364f347 100644
--- a/Sources/WalletConnectVerify/VerifyClient.swift
+++ b/Sources/WalletConnectVerify/VerifyClient.swift
@@ -38,11 +38,19 @@ public actor VerifyClient: VerifyClientProtocol {
}
nonisolated public func createVerifyContext(origin: String?, domain: String) -> VerifyContext {
- return VerifyContext(
- origin: origin,
- validation: (origin == domain) ? .valid : (origin == nil ? .unknown : .invalid),
- verifyUrl: verifyHost
- )
+ if let origin, let originUrl = URL(string: origin), let domainUrl = URL(string: domain) {
+ return VerifyContext(
+ origin: origin,
+ validation: (originUrl.host == domainUrl.host) ? .valid : .invalid,
+ verifyUrl: verifyHost
+ )
+ } else {
+ return VerifyContext(
+ origin: origin,
+ validation: .unknown,
+ verifyUrl: verifyHost
+ )
+ }
}
public func registerAssertion() async throws {
diff --git a/Sources/WalletConnectVerify/VerifyContext.swift b/Sources/WalletConnectVerify/VerifyContext.swift
index 094ea95ab..62b0be4a2 100644
--- a/Sources/WalletConnectVerify/VerifyContext.swift
+++ b/Sources/WalletConnectVerify/VerifyContext.swift
@@ -1,5 +1,5 @@
-public struct VerifyContext: Equatable, Hashable {
- public enum ValidationStatus {
+public struct VerifyContext: Equatable, Hashable, Codable {
+ public enum ValidationStatus: Codable {
case unknown
case valid
case invalid
diff --git a/Sources/Web3Inbox/ChatClient/ChatClientProxy.swift b/Sources/Web3Inbox/ChatClient/ChatClientProxy.swift
index ef51bd3b4..b6fd91d00 100644
--- a/Sources/Web3Inbox/ChatClient/ChatClientProxy.swift
+++ b/Sources/Web3Inbox/ChatClient/ChatClientProxy.swift
@@ -5,7 +5,7 @@ final class ChatClientProxy {
private let client: ChatClient
var onSign: SigningCallback
- var onResponse: ((RPCResponse) async throws -> Void)?
+ var onResponse: ((RPCResponse, RPCRequest) async throws -> Void)?
init(client: ChatClient, onSign: @escaping SigningCallback) {
self.client = client
@@ -119,6 +119,6 @@ private extension ChatClientProxy {
func respond(with object: Object = Blob(), request: RPCRequest) async throws {
let response = RPCResponse(matchingRequest: request, result: object)
- try await onResponse?(response)
+ try await onResponse?(response, request)
}
}
diff --git a/Sources/Web3Inbox/ConfigParam.swift b/Sources/Web3Inbox/ConfigParam.swift
index 429be268d..ad2ff6db7 100644
--- a/Sources/Web3Inbox/ConfigParam.swift
+++ b/Sources/Web3Inbox/ConfigParam.swift
@@ -3,6 +3,6 @@ import Foundation
public enum ConfigParam {
case chatEnabled
- case pushEnabled
+ case notifyEnabled
case settingsEnabled
}
diff --git a/Sources/Web3Inbox/PushClientProxy/PushClientProxy.swift b/Sources/Web3Inbox/NotifyClientProxy/NotifyClientProxy.swift
similarity index 68%
rename from Sources/Web3Inbox/PushClientProxy/PushClientProxy.swift
rename to Sources/Web3Inbox/NotifyClientProxy/NotifyClientProxy.swift
index 8ad36f8ba..4a2a39138 100644
--- a/Sources/Web3Inbox/PushClientProxy/PushClientProxy.swift
+++ b/Sources/Web3Inbox/NotifyClientProxy/NotifyClientProxy.swift
@@ -1,36 +1,28 @@
import Foundation
-final class PushClientProxy {
+final class NotifyClientProxy {
- private let client: WalletPushClient
+ private let client: NotifyClient
var onSign: SigningCallback
- var onResponse: ((RPCResponse) async throws -> Void)?
+ var onResponse: ((RPCResponse, RPCRequest) async throws -> Void)?
- init(client: WalletPushClient, onSign: @escaping SigningCallback) {
+ init(client: NotifyClient, onSign: @escaping SigningCallback) {
self.client = client
self.onSign = onSign
}
func request(_ request: RPCRequest) async throws {
- guard let event = PushWebViewEvent(rawValue: request.method)
+ guard let event = NotifyWebViewEvent(rawValue: request.method)
else { throw Errors.unregisteredMethod }
// TODO: Handle register event
switch event {
- case .approve:
- let params = try parse(ApproveRequest.self, params: request.params)
- try await client.approve(id: params.id, onSign: onSign)
- try await respond(request: request)
case .update:
let params = try parse(UpdateRequest.self, params: request.params)
try await client.update(topic: params.topic, scope: params.scope)
try await respond(request: request)
- case .reject:
- let params = try parse(RejectRequest.self, params: request.params)
- try await client.reject(id: params.id)
- try await respond(request: request)
case .subscribe:
let params = try parse(SubscribeRequest.self, params: request.params)
try await client.subscribe(metadata: params.metadata, account: params.account, onSign: onSign)
@@ -46,19 +38,19 @@ final class PushClientProxy {
let params = try parse(DeleteSubscriptionRequest.self, params: request.params)
try await client.deleteSubscription(topic: params.topic)
try await respond(request: request)
- case .deletePushMessage:
- let params = try parse(DeletePushMessageRequest.self, params: request.params)
- client.deletePushMessage(id: params.id.string)
+ case .deleteNotifyMessage:
+ let params = try parse(DeleteNotifyMessageRequest.self, params: request.params)
+ client.deleteNotifyMessage(id: params.id.string)
try await respond(request: request)
- case .enableSync:
- let params = try parse(EnableSyncRequest.self, params: request.params)
- try await client.enableSync(account: params.account, onSign: onSign)
+ case .register:
+ let params = try parse(RegisterRequest.self, params: request.params)
+ try await client.register(account: params.account, onSign: onSign)
try await respond(request: request)
}
}
}
-private extension PushClientProxy {
+private extension NotifyClientProxy {
private typealias Blob = Dictionary
@@ -93,11 +85,11 @@ private extension PushClientProxy {
let topic: String
}
- struct DeletePushMessageRequest: Codable {
+ struct DeleteNotifyMessageRequest: Codable {
let id: RPCID
}
- struct EnableSyncRequest: Codable {
+ struct RegisterRequest: Codable {
let account: Account
}
@@ -109,6 +101,6 @@ private extension PushClientProxy {
func respond(with object: Object = Blob(), request: RPCRequest) async throws {
let response = RPCResponse(matchingRequest: request, result: object)
- try await onResponse?(response)
+ try await onResponse?(response, request)
}
}
diff --git a/Sources/Web3Inbox/NotifyClientProxy/NotifyClientRequest.swift b/Sources/Web3Inbox/NotifyClientProxy/NotifyClientRequest.swift
new file mode 100644
index 000000000..b152330ce
--- /dev/null
+++ b/Sources/Web3Inbox/NotifyClientProxy/NotifyClientRequest.swift
@@ -0,0 +1,12 @@
+import Foundation
+
+enum NotifyClientRequest: String {
+ case notifyMessage = "notify_message"
+ case notifyUpdate = "notify_update"
+ case notifyDelete = "notify_delete"
+ case notifySubscription = "notify_subscription"
+
+ var method: String {
+ return rawValue
+ }
+}
diff --git a/Sources/Web3Inbox/NotifyClientProxy/NotifyClientRequestSubscriber.swift b/Sources/Web3Inbox/NotifyClientProxy/NotifyClientRequestSubscriber.swift
new file mode 100644
index 000000000..8b255bd81
--- /dev/null
+++ b/Sources/Web3Inbox/NotifyClientProxy/NotifyClientRequestSubscriber.swift
@@ -0,0 +1,60 @@
+import Foundation
+import Combine
+
+final class NotifyClientRequestSubscriber {
+
+ private var publishers: Set = []
+
+ private let client: NotifyClient
+ private let logger: ConsoleLogging
+
+ var onRequest: ((RPCRequest) async throws -> Void)?
+
+ init(client: NotifyClient, logger: ConsoleLogging) {
+ self.client = client
+ self.logger = logger
+
+ setupSubscriptions()
+ }
+
+ func setupSubscriptions() {
+ client.notifyMessagePublisher.sink { [unowned self] record in
+ handle(event: .notifyMessage, params: record.message)
+ }.store(in: &publishers)
+
+ client.newSubscriptionPublisher.sink { [unowned self] subscription in
+ handle(event: .notifySubscription, params: subscription)
+ }.store(in: &publishers)
+
+ client.deleteSubscriptionPublisher.sink { [unowned self] topic in
+ handle(event: .notifyDelete, params: topic)
+ }.store(in: &publishers)
+
+ client.updateSubscriptionPublisher.sink { [unowned self] subscription in
+ handle(event: .notifyUpdate, params: subscription)
+ }.store(in: &publishers)
+ }
+}
+
+private extension NotifyClientRequestSubscriber {
+
+ struct RequestPayload: Codable {
+ let id: RPCID
+ let account: Account
+ let metadata: AppMetadata
+ }
+
+ func handle(event: NotifyClientRequest, params: Codable) {
+ Task {
+ do {
+ let request = RPCRequest(
+ method: event.method,
+ params: params
+ )
+ try await onRequest?(request)
+ } catch {
+ logger.error("Client Request error: \(error.localizedDescription)")
+ }
+ }
+ }
+}
diff --git a/Sources/Web3Inbox/PushClientProxy/PushClientRequest.swift b/Sources/Web3Inbox/PushClientProxy/PushClientRequest.swift
deleted file mode 100644
index e03e3378c..000000000
--- a/Sources/Web3Inbox/PushClientProxy/PushClientRequest.swift
+++ /dev/null
@@ -1,13 +0,0 @@
-import Foundation
-
-enum PushClientRequest: String {
- case pushRequest = "push_request"
- case pushMessage = "push_message"
- case pushUpdate = "push_update"
- case pushDelete = "push_delete"
- case pushSubscription = "push_subscription"
-
- var method: String {
- return rawValue
- }
-}
diff --git a/Sources/Web3Inbox/PushClientProxy/PushClientRequestSubscriber.swift b/Sources/Web3Inbox/PushClientProxy/PushClientRequestSubscriber.swift
deleted file mode 100644
index ad77322d5..000000000
--- a/Sources/Web3Inbox/PushClientProxy/PushClientRequestSubscriber.swift
+++ /dev/null
@@ -1,75 +0,0 @@
-import Foundation
-import Combine
-
-final class PushClientRequestSubscriber {
-
- private var publishers: Set = []
-
- private let client: WalletPushClient
- private let logger: ConsoleLogging
-
- var onRequest: ((RPCRequest) async throws -> Void)?
-
- init(client: WalletPushClient, logger: ConsoleLogging) {
- self.client = client
- self.logger = logger
-
- setupSubscriptions()
- }
-
- func setupSubscriptions() {
- client.requestPublisher.sink { [unowned self] id, account, metadata in
- let params = RequestPayload(id: id, account: account, metadata: metadata)
- handle(event: .pushRequest, params: params)
- }.store(in: &publishers)
-
- client.pushMessagePublisher.sink { [unowned self] record in
- handle(event: .pushMessage, params: record.message)
- }.store(in: &publishers)
-
- client.deleteSubscriptionPublisher.sink { [unowned self] record in
- handle(event: .pushDelete, params: record)
- }.store(in: &publishers)
-
- client.newSubscriptionPublisher.sink { [unowned self] subscription in
- handle(event: .pushSubscription, params: subscription)
- }.store(in: &publishers)
-
- client.deleteSubscriptionPublisher.sink { [unowned self] topic in
- handle(event: .pushDelete, params: topic)
- }.store(in: &publishers)
-
- client.updateSubscriptionPublisher.sink { [unowned self] record in
- switch record {
- case .success(let subscription):
- handle(event: .pushUpdate, params: subscription)
- case .failure:
- //TODO - handle error
- break
- }
- }.store(in: &publishers)
- }
-}
-
-private extension PushClientRequestSubscriber {
-
- struct RequestPayload: Codable {
- let id: RPCID
- let account: Account
- let metadata: AppMetadata
- }
-
- func handle(event: PushClientRequest, params: Codable) {
- Task {
- do {
- let request = RPCRequest(
- method: event.method,
- params: params
- )
- try await onRequest?(request)
- } catch {
- logger.error("Client Request error: \(error.localizedDescription)")
- }
- }
- }
-}
diff --git a/Sources/Web3Inbox/Web3Inbox.swift b/Sources/Web3Inbox/Web3Inbox.swift
index e2555049e..17bbc7f55 100644
--- a/Sources/Web3Inbox/Web3Inbox.swift
+++ b/Sources/Web3Inbox/Web3Inbox.swift
@@ -7,7 +7,7 @@ public final class Web3Inbox {
guard let account, let config = config, let onSign else {
fatalError("Error - you must call Web3Inbox.configure(_:) before accessing the shared instance.")
}
- return Web3InboxClientFactory.create(chatClient: Chat.instance, pushClient: Push.wallet, account: account, config: config, onSign: onSign)
+ return Web3InboxClientFactory.create(chatClient: Chat.instance, notifyClient: Notify.instance, account: account, config: config, onSign: onSign)
}()
private static var account: Account?
@@ -22,12 +22,13 @@ public final class Web3Inbox {
bip44: BIP44Provider,
config: [ConfigParam: Bool] = [:],
environment: APNSEnvironment,
+ crypto: CryptoProvider,
onSign: @escaping SigningCallback
) {
Web3Inbox.account = account
Web3Inbox.config = config
Web3Inbox.onSign = onSign
Chat.configure(bip44: bip44)
- Push.configure(environment: environment)
+ Notify.configure(environment: environment, crypto: crypto)
}
}
diff --git a/Sources/Web3Inbox/Web3InboxClient.swift b/Sources/Web3Inbox/Web3InboxClient.swift
index 3a17e4d0e..7475cd74e 100644
--- a/Sources/Web3Inbox/Web3InboxClient.swift
+++ b/Sources/Web3Inbox/Web3InboxClient.swift
@@ -1,24 +1,31 @@
import Foundation
import WebKit
+import Combine
public final class Web3InboxClient {
private let webView: WKWebView
private var account: Account
private let logger: ConsoleLogging
- private let pushClient: WalletPushClient
+ private let notifyClient: NotifyClient
private let chatClientProxy: ChatClientProxy
private let chatClientSubscriber: ChatClientRequestSubscriber
- private let pushClientProxy: PushClientProxy
- private let pushClientSubscriber: PushClientRequestSubscriber
+ private let notifyClientProxy: NotifyClientProxy
+ private let notifyClientSubscriber: NotifyClientRequestSubscriber
private let chatWebviewProxy: WebViewProxy
- private let pushWebviewProxy: WebViewProxy
+ private let notifyWebviewProxy: WebViewProxy
private let webviewSubscriber: WebViewRequestSubscriber
+ public var logsPublisher: AnyPublisher {
+ logger.logsPublisher
+ .merge(with: notifyClient.logsPublisher)
+ .eraseToAnyPublisher()
+ }
+
init(
webView: WKWebView,
account: Account,
@@ -26,11 +33,11 @@ public final class Web3InboxClient {
chatClientProxy: ChatClientProxy,
clientSubscriber: ChatClientRequestSubscriber,
chatWebviewProxy: WebViewProxy,
- pushWebviewProxy: WebViewProxy,
+ notifyWebviewProxy: WebViewProxy,
webviewSubscriber: WebViewRequestSubscriber,
- pushClientProxy: PushClientProxy,
- pushClientSubscriber: PushClientRequestSubscriber,
- pushClient: WalletPushClient
+ notifyClientProxy: NotifyClientProxy,
+ notifyClientSubscriber: NotifyClientRequestSubscriber,
+ notifyClient: NotifyClient
) {
self.webView = webView
self.account = account
@@ -38,14 +45,20 @@ public final class Web3InboxClient {
self.chatClientProxy = chatClientProxy
self.chatClientSubscriber = clientSubscriber
self.chatWebviewProxy = chatWebviewProxy
- self.pushWebviewProxy = pushWebviewProxy
+ self.notifyWebviewProxy = notifyWebviewProxy
self.webviewSubscriber = webviewSubscriber
- self.pushClientProxy = pushClientProxy
- self.pushClientSubscriber = pushClientSubscriber
- self.pushClient = pushClient
+ self.notifyClientProxy = notifyClientProxy
+ self.notifyClientSubscriber = notifyClientSubscriber
+ self.notifyClient = notifyClient
setupSubscriptions()
}
+
+ public func setLogging(level: LoggingLevel) {
+ logger.setLogging(level: level)
+ notifyClient.setLogging(level: .debug)
+ }
+
public func getWebView() -> WKWebView {
return webView
}
@@ -59,7 +72,11 @@ public final class Web3InboxClient {
}
public func register(deviceToken: Data) async throws {
- try await pushClient.register(deviceToken: deviceToken)
+ try await notifyClient.register(deviceToken: deviceToken)
+ }
+
+ public func reload() {
+ webviewSubscriber.reload(webView)
}
}
@@ -71,8 +88,8 @@ private extension Web3InboxClient {
// Chat
- chatClientProxy.onResponse = { [unowned self] response in
- try await self.chatWebviewProxy.respond(response)
+ chatClientProxy.onResponse = { [unowned self] response, request in
+ try await self.chatWebviewProxy.respond(response, request)
}
chatClientSubscriber.onRequest = { [unowned self] request in
@@ -84,19 +101,19 @@ private extension Web3InboxClient {
try await self.chatClientProxy.request(request)
}
- // Push
+ // Notify
- pushClientProxy.onResponse = { [unowned self] response in
- try await self.pushWebviewProxy.respond(response)
+ notifyClientProxy.onResponse = { [unowned self] response, request in
+ try await self.notifyWebviewProxy.respond(response, request)
}
- pushClientSubscriber.onRequest = { [unowned self] request in
- try await self.pushWebviewProxy.request(request)
+ notifyClientSubscriber.onRequest = { [unowned self] request in
+ try await self.notifyWebviewProxy.request(request)
}
- webviewSubscriber.onPushRequest = { [unowned self] request in
- logger.debug("w3i: push method \(request.method) requested")
- try await self.pushClientProxy.request(request)
+ webviewSubscriber.onNotifyRequest = { [unowned self] request in
+ logger.debug("w3i: notify method \(request.method) requested")
+ try await self.notifyClientProxy.request(request)
}
}
diff --git a/Sources/Web3Inbox/Web3InboxClientFactory.swift b/Sources/Web3Inbox/Web3InboxClientFactory.swift
index 70d59071a..83ce998fe 100644
--- a/Sources/Web3Inbox/Web3InboxClientFactory.swift
+++ b/Sources/Web3Inbox/Web3InboxClientFactory.swift
@@ -5,23 +5,24 @@ final class Web3InboxClientFactory {
static func create(
chatClient: ChatClient,
- pushClient: WalletPushClient,
+ notifyClient: NotifyClient,
account: Account,
config: [ConfigParam: Bool],
onSign: @escaping SigningCallback
) -> Web3InboxClient {
let url = buildUrl(account: account, config: config)
- let logger = ConsoleLogger(suffix: "📬", loggingLevel: .debug)
+
+ let logger = ConsoleLogger(prefix: "📬", loggingLevel: .off)
let webviewSubscriber = WebViewRequestSubscriber(url: url, logger: logger)
let webView = WebViewFactory(url: url, webviewSubscriber: webviewSubscriber).create()
let chatWebViewProxy = WebViewProxy(webView: webView, scriptFormatter: ChatWebViewScriptFormatter(), logger: logger)
- let pushWebViewProxy = WebViewProxy(webView: webView, scriptFormatter: PushWebViewScriptFormatter(), logger: logger)
+ let notifyWebViewProxy = WebViewProxy(webView: webView, scriptFormatter: NotifyWebViewScriptFormatter(), logger: logger)
let clientProxy = ChatClientProxy(client: chatClient, onSign: onSign)
let clientSubscriber = ChatClientRequestSubscriber(chatClient: chatClient, logger: logger)
- let pushClientProxy = PushClientProxy(client: pushClient, onSign: onSign)
- let pushClientSubscriber = PushClientRequestSubscriber(client: pushClient, logger: logger)
+ let notifyClientProxy = NotifyClientProxy(client: notifyClient, onSign: onSign)
+ let notifyClientSubscriber = NotifyClientRequestSubscriber(client: notifyClient, logger: logger)
return Web3InboxClient(
webView: webView,
@@ -30,17 +31,17 @@ final class Web3InboxClientFactory {
chatClientProxy: clientProxy,
clientSubscriber: clientSubscriber,
chatWebviewProxy: chatWebViewProxy,
- pushWebviewProxy: pushWebViewProxy,
+ notifyWebviewProxy: notifyWebViewProxy,
webviewSubscriber: webviewSubscriber,
- pushClientProxy: pushClientProxy,
- pushClientSubscriber: pushClientSubscriber,
- pushClient: pushClient
+ notifyClientProxy: notifyClientProxy,
+ notifyClientSubscriber: notifyClientSubscriber,
+ notifyClient: notifyClient
)
}
private static func buildUrl(account: Account, config: [ConfigParam: Bool]) -> URL {
var urlComponents = URLComponents(string: "https://web3inbox-dev-hidden.vercel.app/")!
- var queryItems = [URLQueryItem(name: "chatProvider", value: "ios"), URLQueryItem(name: "pushProvider", value: "ios"), URLQueryItem(name: "account", value: account.address), URLQueryItem(name: "authProvider", value: "ios")]
+ var queryItems = [URLQueryItem(name: "chatProvider", value: "ios"), URLQueryItem(name: "notifyProvider", value: "ios"), URLQueryItem(name: "account", value: account.address), URLQueryItem(name: "authProvider", value: "ios")]
for param in config.filter({ $0.value == false}) {
queryItems.append(URLQueryItem(name: "\(param.key)", value: "false"))
diff --git a/Sources/Web3Inbox/Web3InboxImports.swift b/Sources/Web3Inbox/Web3InboxImports.swift
index fd78f4977..b03055b3f 100644
--- a/Sources/Web3Inbox/Web3InboxImports.swift
+++ b/Sources/Web3Inbox/Web3InboxImports.swift
@@ -1,4 +1,4 @@
#if !CocoaPods
@_exported import WalletConnectChat
-@_exported import WalletConnectPush
+@_exported import WalletConnectNotify
#endif
diff --git a/Sources/Web3Inbox/WebView/PushWebViewEvent.swift b/Sources/Web3Inbox/WebView/NotifyWebViewEvent.swift
similarity index 56%
rename from Sources/Web3Inbox/WebView/PushWebViewEvent.swift
rename to Sources/Web3Inbox/WebView/NotifyWebViewEvent.swift
index d320a88d7..e9a8e954b 100644
--- a/Sources/Web3Inbox/WebView/PushWebViewEvent.swift
+++ b/Sources/Web3Inbox/WebView/NotifyWebViewEvent.swift
@@ -1,13 +1,11 @@
import Foundation
-enum PushWebViewEvent: String {
- case approve
+enum NotifyWebViewEvent: String {
case update
- case reject
case subscribe
case getActiveSubscriptions
case getMessageHistory
case deleteSubscription
- case deletePushMessage
- case enableSync
+ case deleteNotifyMessage
+ case register
}
diff --git a/Sources/Web3Inbox/WebView/WebViewFactory.swift b/Sources/Web3Inbox/WebView/WebViewFactory.swift
index 5a6fc90ad..1d8cf45c1 100644
--- a/Sources/Web3Inbox/WebView/WebViewFactory.swift
+++ b/Sources/Web3Inbox/WebView/WebViewFactory.swift
@@ -22,7 +22,7 @@ final class WebViewFactory {
)
configuration.userContentController.add(
webviewSubscriber,
- name: WebViewRequestSubscriber.push
+ name: WebViewRequestSubscriber.notify
)
let webview = WKWebView(frame: .zero, configuration: configuration)
diff --git a/Sources/Web3Inbox/WebView/WebViewProxy.swift b/Sources/Web3Inbox/WebView/WebViewProxy.swift
index af3d0d8d7..5f4612701 100644
--- a/Sources/Web3Inbox/WebView/WebViewProxy.swift
+++ b/Sources/Web3Inbox/WebView/WebViewProxy.swift
@@ -17,9 +17,10 @@ actor WebViewProxy {
}
@MainActor
- func respond(_ response: RPCResponse) async throws {
+ func respond(_ response: RPCResponse, _ request: RPCRequest) async throws {
let body = try response.json(dateEncodingStrategy: .millisecondsSince1970)
- logger.debug("resonding to w3i with \(body)")
+ let logProperties: [String: String] = ["method": request.method, "requestId": "\(request.id!)", "response": body]
+ logger.debug("resonding to w3i request \(request.method) with \(body)", properties: logProperties)
let script = scriptFormatter.formatScript(body: body)
webView.evaluateJavaScript(script, completionHandler: nil)
}
@@ -27,7 +28,8 @@ actor WebViewProxy {
@MainActor
func request(_ request: RPCRequest) async throws {
let body = try request.json(dateEncodingStrategy: .millisecondsSince1970)
- logger.debug("requesting w3i with \(body)")
+ let logProperties = ["method": request.method, "requestId": "\(request.id!)"]
+ logger.debug("requesting w3i with \(body)", properties: logProperties)
let script = scriptFormatter.formatScript(body: body)
webView.evaluateJavaScript(script, completionHandler: nil)
}
@@ -44,8 +46,8 @@ class ChatWebViewScriptFormatter: WebViewScriptFormatter {
}
}
-class PushWebViewScriptFormatter: WebViewScriptFormatter {
+class NotifyWebViewScriptFormatter: WebViewScriptFormatter {
func formatScript(body: String) -> String {
- return "window.web3inbox.push.postMessage(\(body))"
+ return "window.web3inbox.notify.postMessage(\(body))"
}
}
diff --git a/Sources/Web3Inbox/WebView/WebViewRequestSubscriber.swift b/Sources/Web3Inbox/WebView/WebViewRequestSubscriber.swift
index 7c5307221..49fc5a151 100644
--- a/Sources/Web3Inbox/WebView/WebViewRequestSubscriber.swift
+++ b/Sources/Web3Inbox/WebView/WebViewRequestSubscriber.swift
@@ -4,10 +4,10 @@ import WebKit
final class WebViewRequestSubscriber: NSObject, WKScriptMessageHandler {
static let chat = "web3inboxChat"
- static let push = "web3inboxPush"
+ static let notify = "web3inboxNotify"
var onChatRequest: ((RPCRequest) async throws -> Void)?
- var onPushRequest: ((RPCRequest) async throws -> Void)?
+ var onNotifyRequest: ((RPCRequest) async throws -> Void)?
private let url: URL
private let logger: ConsoleLogging
@@ -35,8 +35,8 @@ final class WebViewRequestSubscriber: NSObject, WKScriptMessageHandler {
switch name {
case Self.chat:
try await onChatRequest?(request)
- case Self.push:
- try await onPushRequest?(request)
+ case Self.notify:
+ try await onNotifyRequest?(request)
default:
break
}
@@ -45,31 +45,32 @@ final class WebViewRequestSubscriber: NSObject, WKScriptMessageHandler {
}
}
}
+
+ func reload(_ webView: WKWebView) {
+ webView.load(URLRequest(url: url))
+ }
}
extension WebViewRequestSubscriber: WKUIDelegate {
-
+
+ #if os(iOS)
+
@available(iOS 15.0, *)
func webView(_ webView: WKWebView, requestMediaCapturePermissionFor origin: WKSecurityOrigin, initiatedByFrame frame: WKFrameInfo, type: WKMediaCaptureType, decisionHandler: @escaping (WKPermissionDecision) -> Void) {
decisionHandler(.grant)
}
+
+ #endif
}
extension WebViewRequestSubscriber: WKNavigationDelegate {
func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) {
- guard
- let from = webView.url,
- let to = navigationAction.request.url
- else { return decisionHandler(.cancel) }
-
- if from.absoluteString.contains("/login") || to.absoluteString.contains("/login") {
- decisionHandler(.cancel)
- webView.load(URLRequest(url: url))
- } else {
+ if navigationAction.request.url == url {
decisionHandler(.allow)
+ } else {
+ decisionHandler(.cancel)
}
}
}
-
diff --git a/Sources/Web3Wallet/Web3Wallet.swift b/Sources/Web3Wallet/Web3Wallet.swift
index 5a7c2e0a5..a00f42b4b 100644
--- a/Sources/Web3Wallet/Web3Wallet.swift
+++ b/Sources/Web3Wallet/Web3Wallet.swift
@@ -27,7 +27,7 @@ public class Web3Wallet {
authClient: Auth.instance,
signClient: Sign.instance,
pairingClient: Pair.instance as! PairingClient,
- echoClient: Echo.instance
+ pushClient: Push.instance
)
}()
@@ -42,12 +42,12 @@ public class Web3Wallet {
static public func configure(
metadata: AppMetadata,
crypto: CryptoProvider,
- echoHost: String = "echo.walletconnect.com",
+ pushHost: String = "echo.walletconnect.com",
environment: APNSEnvironment = .production
) {
Pair.configure(metadata: metadata)
Auth.configure(crypto: crypto)
- Echo.configure(echoHost: echoHost, environment: environment)
+ Push.configure(pushHost: pushHost, environment: environment)
Web3Wallet.config = Web3Wallet.Config(crypto: crypto)
}
}
diff --git a/Sources/Web3Wallet/Web3WalletClient.swift b/Sources/Web3Wallet/Web3WalletClient.swift
index 757b15e7f..a90093a10 100644
--- a/Sources/Web3Wallet/Web3WalletClient.swift
+++ b/Sources/Web3Wallet/Web3WalletClient.swift
@@ -67,7 +67,7 @@ public class Web3WalletClient {
private let authClient: AuthClientProtocol
private let signClient: SignClientProtocol
private let pairingClient: PairingClientProtocol
- private let echoClient: EchoClientProtocol
+ private let pushClient: PushClientProtocol
private var account: Account?
@@ -75,12 +75,12 @@ public class Web3WalletClient {
authClient: AuthClientProtocol,
signClient: SignClientProtocol,
pairingClient: PairingClientProtocol,
- echoClient: EchoClientProtocol
+ pushClient: PushClientProtocol
) {
self.authClient = authClient
self.signClient = signClient
self.pairingClient = pairingClient
- self.echoClient = echoClient
+ self.pushClient = pushClient
}
/// For a wallet to approve a session proposal.
@@ -190,24 +190,30 @@ public class Web3WalletClient {
/// Query pending requests
/// - Returns: Pending requests received from peer with `wc_sessionRequest` protocol method
/// - Parameter topic: topic representing session for which you want to get pending requests. If nil, you will receive pending requests for all active sessions.
- public func getPendingRequests(topic: String? = nil) -> [Request] {
+ public func getPendingRequests(topic: String? = nil) -> [(request: Request, context: VerifyContext?)] {
signClient.getPendingRequests(topic: topic)
}
+ /// Query pending proposals
+ /// - Returns: Pending proposals received from peer with `wc_sessionPropose` protocol method
+ public func getPendingProposals(topic: String? = nil) -> [(proposal: Session.Proposal, context: VerifyContext?)] {
+ signClient.getPendingProposals(topic: topic)
+ }
+
/// - Parameter id: id of a wc_sessionRequest jsonrpc request
/// - Returns: json rpc record object for given id or nil if record for give id does not exits
- public func getSessionRequestRecord(id: RPCID) -> Request? {
+ public func getSessionRequestRecord(id: RPCID) -> (request: Request, context: VerifyContext?)? {
signClient.getSessionRequestRecord(id: id)
}
/// Query pending authentication requests
/// - Returns: Pending authentication requests
- public func getPendingRequests() throws -> [AuthRequest] {
+ public func getPendingRequests() throws -> [(AuthRequest, VerifyContext?)] {
try authClient.getPendingRequests()
}
- public func registerEchoClient(deviceToken: Data) async throws {
- try await echoClient.register(deviceToken: deviceToken)
+ public func registerPushClient(deviceToken: Data) async throws {
+ try await pushClient.register(deviceToken: deviceToken)
}
/// Delete all stored data such as: pairings, sessions, keys
@@ -224,8 +230,8 @@ public class Web3WalletClient {
#if DEBUG
extension Web3WalletClient {
- public func registerEchoClient(deviceToken: String) async throws {
- try await echoClient.register(deviceToken: deviceToken)
+ public func registerPushClient(deviceToken: String) async throws {
+ try await pushClient.register(deviceToken: deviceToken)
}
}
#endif
diff --git a/Sources/Web3Wallet/Web3WalletClientFactory.swift b/Sources/Web3Wallet/Web3WalletClientFactory.swift
index b27654757..99b5cc969 100644
--- a/Sources/Web3Wallet/Web3WalletClientFactory.swift
+++ b/Sources/Web3Wallet/Web3WalletClientFactory.swift
@@ -5,13 +5,13 @@ public struct Web3WalletClientFactory {
authClient: AuthClientProtocol,
signClient: SignClientProtocol,
pairingClient: PairingClientProtocol,
- echoClient: EchoClientProtocol
+ pushClient: PushClientProtocol
) -> Web3WalletClient {
return Web3WalletClient(
authClient: authClient,
signClient: signClient,
pairingClient: pairingClient,
- echoClient: echoClient
+ pushClient: pushClient
)
}
}
diff --git a/Sources/Web3Wallet/Web3WalletImports.swift b/Sources/Web3Wallet/Web3WalletImports.swift
index c27041056..f2626dea0 100644
--- a/Sources/Web3Wallet/Web3WalletImports.swift
+++ b/Sources/Web3Wallet/Web3WalletImports.swift
@@ -1,6 +1,6 @@
#if !CocoaPods
@_exported import Auth
@_exported import WalletConnectSign
-@_exported import WalletConnectEcho
+@_exported import WalletConnectPush
@_exported import WalletConnectVerify
#endif
diff --git a/Tests/AuthTests/WalletRequestSubscriberTests.swift b/Tests/AuthTests/WalletRequestSubscriberTests.swift
index abf3b76da..1fba88977 100644
--- a/Tests/AuthTests/WalletRequestSubscriberTests.swift
+++ b/Tests/AuthTests/WalletRequestSubscriberTests.swift
@@ -11,21 +11,31 @@ class WalletRequestSubscriberTests: XCTestCase {
var pairingRegisterer: PairingRegistererMock!
var sut: WalletRequestSubscriber!
var messageFormatter: SIWEMessageFormatterMock!
-
+ var verifyContextStore: CodableStore!
+
let defaultTimeout: TimeInterval = 0.01
override func setUp() {
let networkingInteractor = NetworkingInteractorMock()
pairingRegisterer = PairingRegistererMock()
messageFormatter = SIWEMessageFormatterMock()
-
- let walletErrorResponder = WalletErrorResponder(networkingInteractor: networkingInteractor, logger: ConsoleLoggerMock(), kms: KeyManagementServiceMock(), rpcHistory: RPCHistory(keyValueStore: CodableStore(defaults: RuntimeKeyValueStorage(), identifier: "")))
- sut = WalletRequestSubscriber(networkingInteractor: networkingInteractor,
- logger: ConsoleLoggerMock(),
- kms: KeyManagementServiceMock(),
- walletErrorResponder: walletErrorResponder,
- pairingRegisterer: pairingRegisterer,
- verifyClient: VerifyClientMock())
+ verifyContextStore = CodableStore(defaults: RuntimeKeyValueStorage(), identifier: "")
+
+ let walletErrorResponder = WalletErrorResponder(
+ networkingInteractor: networkingInteractor,
+ logger: ConsoleLoggerMock(),
+ kms: KeyManagementServiceMock(),
+ rpcHistory: RPCHistory(keyValueStore: CodableStore(defaults: RuntimeKeyValueStorage(), identifier: ""))
+ )
+ sut = WalletRequestSubscriber(
+ networkingInteractor: networkingInteractor,
+ logger: ConsoleLoggerMock(),
+ kms: KeyManagementServiceMock(),
+ walletErrorResponder: walletErrorResponder,
+ pairingRegisterer: pairingRegisterer,
+ verifyClient: VerifyClientMock(),
+ verifyContextStore: verifyContextStore
+ )
}
func testSubscribeRequest() {
@@ -47,7 +57,7 @@ class WalletRequestSubscriberTests: XCTestCase {
pairingRegisterer.subject.send(payload)
wait(for: [messageExpectation], timeout: defaultTimeout)
- XCTAssertTrue(pairingRegisterer.isActivateCalled)
+ XCTAssertTrue(pairingRegisterer.isReceivedCalled)
XCTAssertEqual(requestPayload, expectedPayload)
XCTAssertEqual(requestId, expectedRequestId)
}
diff --git a/Tests/NotifyTests/Mocks/MockPushStoring.swift b/Tests/NotifyTests/Mocks/MockNotifyStoring.swift
similarity index 58%
rename from Tests/NotifyTests/Mocks/MockPushStoring.swift
rename to Tests/NotifyTests/Mocks/MockNotifyStoring.swift
index 06dc07960..4f1be0991 100644
--- a/Tests/NotifyTests/Mocks/MockPushStoring.swift
+++ b/Tests/NotifyTests/Mocks/MockNotifyStoring.swift
@@ -1,23 +1,22 @@
-
import Foundation
-@testable import WalletConnectPush
+@testable import WalletConnectNotify
-class MockPushStoring: PushStoring {
- var subscriptions: [PushSubscription]
+class MockNotifyStoring: NotifyStoring {
+ var subscriptions: [NotifySubscription]
- init(subscriptions: [PushSubscription]) {
+ init(subscriptions: [NotifySubscription]) {
self.subscriptions = subscriptions
}
- func getSubscriptions() -> [PushSubscription] {
+ func getSubscriptions() -> [NotifySubscription] {
return subscriptions
}
- func getSubscription(topic: String) -> PushSubscription? {
+ func getSubscription(topic: String) -> NotifySubscription? {
return subscriptions.first { $0.topic == topic }
}
- func setSubscription(_ subscription: PushSubscription) async throws {
+ func setSubscription(_ subscription: NotifySubscription) async throws {
if let index = subscriptions.firstIndex(where: { $0.topic == subscription.topic }) {
subscriptions[index] = subscription
} else {
diff --git a/Tests/NotifyTests/Mocks/MockNotifyUpdateRequester.swift b/Tests/NotifyTests/Mocks/MockNotifyUpdateRequester.swift
index 8f4d698e1..927676953 100644
--- a/Tests/NotifyTests/Mocks/MockNotifyUpdateRequester.swift
+++ b/Tests/NotifyTests/Mocks/MockNotifyUpdateRequester.swift
@@ -1,6 +1,6 @@
import Foundation
-@testable import WalletConnectPush
+@testable import WalletConnectNotify
class MockNotifyUpdateRequester: NotifyUpdateRequesting {
diff --git a/Tests/NotifyTests/Stubs/PushSubscription.swift b/Tests/NotifyTests/Stubs/NotifySubscription.swift
similarity index 74%
rename from Tests/NotifyTests/Stubs/PushSubscription.swift
rename to Tests/NotifyTests/Stubs/NotifySubscription.swift
index de40c5772..7251c8477 100644
--- a/Tests/NotifyTests/Stubs/PushSubscription.swift
+++ b/Tests/NotifyTests/Stubs/NotifySubscription.swift
@@ -1,15 +1,14 @@
-
import Foundation
-@testable import WalletConnectPush
+@testable import WalletConnectNotify
-extension PushSubscription {
- static func stub(topic: String, expiry: Date) -> PushSubscription {
+extension NotifySubscription {
+ static func stub(topic: String, expiry: Date) -> NotifySubscription {
let account = Account(chainIdentifier: "eip155:1", address: "0x15bca56b6e2728aec2532df9d436bd1600e86688")!
let relay = RelayProtocolOptions.stub()
let metadata = AppMetadata.stub()
let symKey = "key1"
- return PushSubscription(
+ return NotifySubscription(
topic: topic,
account: account,
relay: relay,
diff --git a/Tests/NotifyTests/SubscriptionsAutoUpdaterTests.swift b/Tests/NotifyTests/SubscriptionsAutoUpdaterTests.swift
index 60f0c09c3..357281daf 100644
--- a/Tests/NotifyTests/SubscriptionsAutoUpdaterTests.swift
+++ b/Tests/NotifyTests/SubscriptionsAutoUpdaterTests.swift
@@ -1,33 +1,34 @@
import Foundation
import XCTest
import TestingUtils
-@testable import WalletConnectPush
+@testable import WalletConnectNotify
class SubscriptionsAutoUpdaterTests: XCTestCase {
var sut: SubscriptionsAutoUpdater!
func testUpdateSubscriptionsIfNeeded() async {
- let subscriptions: [PushSubscription] = [
- PushSubscription.stub(topic: "topic1", expiry: Date().addingTimeInterval(60 * 60 * 24 * 20)),
- PushSubscription.stub(topic: "topic2", expiry: Date().addingTimeInterval(60 * 60 * 24 * 10)),
- PushSubscription.stub(topic: "topic3", expiry: Date().addingTimeInterval(60 * 60 * 24 * 30))
+ let subscriptions: [NotifySubscription] = [
+ NotifySubscription.stub(topic: "topic1", expiry: Date().addingTimeInterval(60 * 60 * 24 * 20)),
+ NotifySubscription.stub(topic: "topic2", expiry: Date().addingTimeInterval(60 * 60 * 24 * 10)),
+ NotifySubscription.stub(topic: "topic3", expiry: Date().addingTimeInterval(60 * 60 * 24 * 30))
]
let expectation = expectation(description: "update")
let notifyUpdateRequester = MockNotifyUpdateRequester()
let logger = ConsoleLoggerMock()
- let pushStorage = MockPushStoring(subscriptions: subscriptions)
+ let pushStorage = MockNotifyStoring(subscriptions: subscriptions)
notifyUpdateRequester.completionHandler = {
if notifyUpdateRequester.updatedTopics.contains("topic2") {
expectation.fulfill()
}
}
-
- sut = SubscriptionsAutoUpdater(notifyUpdateRequester: notifyUpdateRequester,
- logger: logger,
- pushStorage: pushStorage)
+
+ sut = SubscriptionsAutoUpdater(
+ notifyUpdateRequester: notifyUpdateRequester,
+ logger: logger,
+ notifyStorage: pushStorage)
await waitForExpectations(timeout: 1, handler: nil)
diff --git a/Tests/RelayerTests/AuthTests/JWTTests.swift b/Tests/RelayerTests/AuthTests/JWTTests.swift
index 83f8ebf4c..40ad5be31 100644
--- a/Tests/RelayerTests/AuthTests/JWTTests.swift
+++ b/Tests/RelayerTests/AuthTests/JWTTests.swift
@@ -36,6 +36,6 @@ extension RelayAuthPayload.Claims {
let aud = "wss://relay.walletconnect.com"
let expDate = Calendar.current.date(byAdding: components, to: iatDate)!
let exp = UInt64(expDate.timeIntervalSince1970)
- return RelayAuthPayload.Claims(iss: iss, sub: sub, aud: aud, iat: iat, exp: exp)
+ return RelayAuthPayload.Claims(iss: iss, sub: sub, aud: aud, iat: iat, exp: exp, act: nil)
}
}
diff --git a/Tests/RelayerTests/AutomaticSocketConnectionHandlerTests.swift b/Tests/RelayerTests/AutomaticSocketConnectionHandlerTests.swift
index 92c0895af..12f7c1d94 100644
--- a/Tests/RelayerTests/AutomaticSocketConnectionHandlerTests.swift
+++ b/Tests/RelayerTests/AutomaticSocketConnectionHandlerTests.swift
@@ -24,7 +24,7 @@ final class AutomaticSocketConnectionHandlerTests: XCTestCase {
func testConnectsOnConnectionSatisfied() {
webSocketSession.disconnect()
XCTAssertFalse(webSocketSession.isConnected)
- networkMonitor.onSatisfied?()
+ networkMonitor.networkConnectionStatusPublisherSubject.send(.connected)
XCTAssertTrue(webSocketSession.isConnected)
}
diff --git a/Tests/RelayerTests/Mocks/NetworkMonitoringMock.swift b/Tests/RelayerTests/Mocks/NetworkMonitoringMock.swift
index 7f6a5245e..1095d1677 100644
--- a/Tests/RelayerTests/Mocks/NetworkMonitoringMock.swift
+++ b/Tests/RelayerTests/Mocks/NetworkMonitoringMock.swift
@@ -1,9 +1,14 @@
import Foundation
+import Combine
+
@testable import WalletConnectRelay
class NetworkMonitoringMock: NetworkMonitoring {
- var onSatisfied: (() -> Void)?
- var onUnsatisfied: (() -> Void)?
-
- func startMonitoring() { }
+ var networkConnectionStatusPublisher: AnyPublisher {
+ networkConnectionStatusPublisherSubject.eraseToAnyPublisher()
+ }
+
+ let networkConnectionStatusPublisherSubject = CurrentValueSubject(.connected)
+
+ public init() { }
}
diff --git a/Tests/TestingUtils/ConsoleLoggerMock.swift b/Tests/TestingUtils/ConsoleLoggerMock.swift
deleted file mode 100644
index 9be922bf1..000000000
--- a/Tests/TestingUtils/ConsoleLoggerMock.swift
+++ /dev/null
@@ -1,11 +0,0 @@
-import Foundation
-import WalletConnectUtils
-
-public struct ConsoleLoggerMock: ConsoleLogging {
- public init() {}
- public func error(_ items: Any...) { }
- public func debug(_ items: Any...) { }
- public func info(_ items: Any...) { }
- public func warn(_ items: Any...) { }
- public func setLogging(level: LoggingLevel) { }
-}
diff --git a/Tests/TestingUtils/Mocks/HTTPClientMock.swift b/Tests/TestingUtils/Mocks/HTTPClientMock.swift
index 4746bed92..5593b4096 100644
--- a/Tests/TestingUtils/Mocks/HTTPClientMock.swift
+++ b/Tests/TestingUtils/Mocks/HTTPClientMock.swift
@@ -2,7 +2,6 @@ import Foundation
@testable import HTTPClient
public final class HTTPClientMock: HTTPClient {
-
private let object: T
public init(object: T) {
@@ -16,4 +15,8 @@ public final class HTTPClientMock: HTTPClient {
public func request(service: HTTPService) async throws {
}
+
+ public func updateHost(host: String) async {
+
+ }
}
diff --git a/Tests/TestingUtils/Mocks/PairingRegistererMock.swift b/Tests/TestingUtils/Mocks/PairingRegistererMock.swift
index ee0cfec9b..3c21567c6 100644
--- a/Tests/TestingUtils/Mocks/PairingRegistererMock.swift
+++ b/Tests/TestingUtils/Mocks/PairingRegistererMock.swift
@@ -4,10 +4,10 @@ import Combine
import WalletConnectNetworking
public class PairingRegistererMock: PairingRegisterer where RequestParams: Codable {
-
public let subject = PassthroughSubject, Never>()
public var isActivateCalled: Bool = false
+ public var isReceivedCalled: Bool = false
public func register(method: ProtocolMethod) -> AnyPublisher, Never> where RequestParams: Decodable, RequestParams: Encodable {
subject.eraseToAnyPublisher() as! AnyPublisher, Never>
@@ -20,4 +20,8 @@ public class PairingRegistererMock: PairingRegisterer where Reque
public func validatePairingExistance(_ topic: String) throws {
}
+
+ public func setReceived(pairingTopic: String) {
+ isReceivedCalled = true
+ }
}
diff --git a/Tests/TestingUtils/NetworkingInteractorMock.swift b/Tests/TestingUtils/NetworkingInteractorMock.swift
index 14f7f9d24..8764e7675 100644
--- a/Tests/TestingUtils/NetworkingInteractorMock.swift
+++ b/Tests/TestingUtils/NetworkingInteractorMock.swift
@@ -15,6 +15,7 @@ public class NetworkingInteractorMock: NetworkInteracting {
private(set) var didRespondError = false
private(set) var didCallSubscribe = false
private(set) var didCallUnsubscribe = false
+ private(set) var didCallHandleHistoryRequest = false
private(set) var didRespondOnTopic: String?
private(set) var lastErrorCode = -1
@@ -25,9 +26,14 @@ public class NetworkingInteractorMock: NetworkInteracting {
var onRespondError: ((Int) -> Void)?
public let socketConnectionStatusPublisherSubject = PassthroughSubject()
+ public let networkConnectionStatusPublisherSubject = CurrentValueSubject(.connected)
+
public var socketConnectionStatusPublisher: AnyPublisher {
socketConnectionStatusPublisherSubject.eraseToAnyPublisher()
}
+ public var networkConnectionStatusPublisher: AnyPublisher {
+ networkConnectionStatusPublisherSubject.eraseToAnyPublisher()
+ }
public let requestPublisherSubject = PassthroughSubject<(topic: String, request: RPCRequest, decryptedPayload: Data, publishedAt: Date, derivedTopic: String?), Never>()
public let responsePublisherSubject = PassthroughSubject<(topic: String, request: RPCRequest, response: RPCResponse, publishedAt: Date, derivedTopic: String?), Never>()
@@ -85,6 +91,10 @@ public class NetworkingInteractorMock: NetworkInteracting {
subscriptions.append(topic)
didCallSubscribe = true
}
+
+ public func handleHistoryRequest(topic: String, request: JSONRPC.RPCRequest) {
+ didCallHandleHistoryRequest = true
+ }
func didSubscribe(to topic: String) -> Bool {
subscriptions.contains { $0 == topic }
diff --git a/Tests/WalletConnectKMSTests/SerialiserTests.swift b/Tests/WalletConnectKMSTests/SerialiserTests.swift
index 627171b5a..bf36da69d 100644
--- a/Tests/WalletConnectKMSTests/SerialiserTests.swift
+++ b/Tests/WalletConnectKMSTests/SerialiserTests.swift
@@ -12,10 +12,10 @@ final class SerializerTests: XCTestCase {
override func setUp() {
self.myKms = KeyManagementServiceMock()
- self.mySerializer = Serializer(kms: myKms)
+ self.mySerializer = Serializer(kms: myKms, logger: ConsoleLoggerMock())
self.peerKms = KeyManagementServiceMock()
- self.peerSerializer = Serializer(kms: peerKms)
+ self.peerSerializer = Serializer(kms: peerKms, logger: ConsoleLoggerMock())
}
func testSerializeDeserializeType0Envelope() {
diff --git a/Tests/WalletConnectModalTests/ModalViewModelTests.swift b/Tests/WalletConnectModalTests/ModalViewModelTests.swift
index ae020953e..55de25cc9 100644
--- a/Tests/WalletConnectModalTests/ModalViewModelTests.swift
+++ b/Tests/WalletConnectModalTests/ModalViewModelTests.swift
@@ -18,11 +18,47 @@ final class ModalViewModelTests: XCTestCase {
sut = .init(
isShown: .constant(true),
interactor: ModalSheetInteractorMock(listings: [
- Listing(id: "1", name: "Sample App", homepage: "https://example.com", order: 1, imageId: "1", app: Listing.App(ios: "https://example.com/download-ios", mac: "https://example.com/download-mac", safari: "https://example.com/download-safari"), mobile: Listing.Mobile(native: nil, universal: "https://example.com/universal")),
- Listing(id: "2", name: "Awesome App", homepage: "https://example.com/awesome", order: 2, imageId: "2", app: Listing.App(ios: "https://example.com/download-ios", mac: "https://example.com/download-mac", safari: "https://example.com/download-safari"), mobile: Listing.Mobile(native: "awesomeapp://deeplink", universal: "https://awesome.com/awesome/universal")),
+ Listing(
+ id: "1",
+ name: "Sample App",
+ homepage: "https://example.com",
+ order: 1,
+ imageId: "1",
+ app: Listing.App(
+ ios: "https://example.com/download-ios",
+ browser: "https://example.com/wallet"
+ ),
+ mobile: Listing.Links(
+ native: nil,
+ universal: "https://example.com/universal"
+ ),
+ desktop: Listing.Links(
+ native: nil,
+ universal: "https://example.com/universal"
+ )
+ ),
+ Listing(
+ id: "2",
+ name: "Awesome App",
+ homepage: "https://example.com/awesome",
+ order: 2,
+ imageId: "2",
+ app: Listing.App(
+ ios: "https://example.com/download-ios",
+ browser: "https://example.com/wallet"
+ ),
+ mobile: Listing.Links(
+ native: "awesomeapp://deeplink",
+ universal: "https://awesome.com/awesome/universal"
+ ),
+ desktop: Listing.Links(
+ native: "awesomeapp://deeplink",
+ universal: "https://awesome.com/awesome/desktop/universal"
+ )
+ ),
]),
uiApplicationWrapper: .init(
- openURL: { url, _ in
+ openURL: { url, _ in
self.openURLFuncTest.call(url)
self.expectation.fulfill()
},
@@ -47,15 +83,13 @@ final class ModalViewModelTests: XCTestCase {
await sut.createURI()
XCTAssertEqual(sut.uri, "wc:foo@2?symKey=bar&relay-protocol=irn")
-
XCTAssertEqual(sut.wallets.count, 2)
XCTAssertEqual(sut.wallets.map(\.id), ["1", "2"])
XCTAssertEqual(sut.wallets.map(\.name), ["Sample App", "Awesome App"])
expectation = XCTestExpectation(description: "Wait for openUrl to be called")
- sut.onListingTap(sut.wallets[0], preferUniversal: true)
-
+ sut.navigateToDeepLink(wallet: sut.wallets[0], preferUniversal: true, preferBrowser: false)
XCTWaiter.wait(for: [expectation], timeout: 3)
XCTAssertEqual(
@@ -65,8 +99,7 @@ final class ModalViewModelTests: XCTestCase {
expectation = XCTestExpectation(description: "Wait for openUrl to be called using universal link")
- sut.onListingTap(sut.wallets[1], preferUniversal: false)
-
+ sut.navigateToDeepLink(wallet: sut.wallets[1], preferUniversal: false, preferBrowser: false)
XCTWaiter.wait(for: [expectation], timeout: 3)
XCTAssertEqual(
@@ -74,16 +107,24 @@ final class ModalViewModelTests: XCTestCase {
URL(string: "awesomeapp://deeplinkwc?uri=wc%3Afoo%402%3FsymKey%3Dbar%26relay-protocol%3Dirn")!
)
-
expectation = XCTestExpectation(description: "Wait for openUrl to be called using native link")
- sut.onListingTap(sut.wallets[1], preferUniversal: true)
-
+ sut.navigateToDeepLink(wallet: sut.wallets[1], preferUniversal: true, preferBrowser: false)
XCTWaiter.wait(for: [expectation], timeout: 3)
XCTAssertEqual(
openURLFuncTest.currentValue,
URL(string: "https://awesome.com/awesome/universal/wc?uri=wc%3Afoo%402%3FsymKey%3Dbar%26relay-protocol%3Dirn")!
)
+
+ expectation = XCTestExpectation(description: "Wait for openUrl to be called using native link")
+
+ sut.navigateToDeepLink(wallet: sut.wallets[1], preferUniversal: false, preferBrowser: true)
+ XCTWaiter.wait(for: [expectation], timeout: 3)
+
+ XCTAssertEqual(
+ openURLFuncTest.currentValue,
+ URL(string: "https://awesome.com/awesome/desktop/universal/wc?uri=wc%3Afoo%402%3FsymKey%3Dbar%26relay-protocol%3Dirn")!
+ )
}
}
diff --git a/Tests/WalletConnectPairingTests/WalletPairServiceTests.swift b/Tests/WalletConnectPairingTests/WalletPairServiceTests.swift
index 7617aee57..4f88e9c94 100644
--- a/Tests/WalletConnectPairingTests/WalletPairServiceTests.swift
+++ b/Tests/WalletConnectPairingTests/WalletPairServiceTests.swift
@@ -11,23 +11,34 @@ final class WalletPairServiceTestsTests: XCTestCase {
var networkingInteractor: NetworkingInteractorMock!
var storageMock: WCPairingStorageMock!
var cryptoMock: KeyManagementServiceMock!
+ var rpcHistory: RPCHistory!
override func setUp() {
networkingInteractor = NetworkingInteractorMock()
storageMock = WCPairingStorageMock()
cryptoMock = KeyManagementServiceMock()
- service = WalletPairService(networkingInteractor: networkingInteractor, kms: cryptoMock, pairingStorage: storageMock)
+ rpcHistory = RPCHistoryFactory.createForNetwork(keyValueStorage: RuntimeKeyValueStorage())
+ service = WalletPairService(networkingInteractor: networkingInteractor, kms: cryptoMock, pairingStorage: storageMock, history: rpcHistory)
+ }
+
+ func testPairWhenNetworkNotConnectedThrows() async {
+ let uri = WalletConnectURI.stub()
+ networkingInteractor.networkConnectionStatusPublisherSubject.send(.notConnected)
+ await XCTAssertThrowsErrorAsync(try await service.pair(uri))
}
- func testPairMultipleTimesOnSameURIThrows() async {
+ func testPairOnSameUriPresentsRequest() async {
+ let rpcRequest = RPCRequest(method: "session_propose", id: 1234)
+
let uri = WalletConnectURI.stub()
- for i in 1...10 {
- if i == 1 {
- await XCTAssertNoThrowAsync(try await service.pair(uri))
- } else {
- await XCTAssertThrowsErrorAsync(try await service.pair(uri))
- }
- }
+ try! await service.pair(uri)
+ var pairing = storageMock.getPairing(forTopic: uri.topic)
+ pairing?.receivedRequest()
+ storageMock.setPairing(pairing!)
+ try! rpcHistory.set(rpcRequest, forTopic: uri.topic, emmitedBy: .local)
+
+ try! await service.pair(uri)
+ XCTAssertTrue(networkingInteractor.didCallHandleHistoryRequest)
}
func testPair() async {
diff --git a/Tests/WalletConnectSignTests/AppProposalServiceTests.swift b/Tests/WalletConnectSignTests/AppProposalServiceTests.swift
index 066473ae9..bdc8c7180 100644
--- a/Tests/WalletConnectSignTests/AppProposalServiceTests.swift
+++ b/Tests/WalletConnectSignTests/AppProposalServiceTests.swift
@@ -62,6 +62,7 @@ final class AppProposalServiceTests: XCTestCase {
approveEngine = ApproveEngine(
networkingInteractor: networkingInteractor,
proposalPayloadsStore: .init(defaults: RuntimeKeyValueStorage(), identifier: ""),
+ verifyContextStore: CodableStore(defaults: RuntimeKeyValueStorage(), identifier: ""),
sessionTopicToProposal: CodableStore(defaults: RuntimeKeyValueStorage(), identifier: ""),
pairingRegisterer: pairingRegisterer,
metadata: meta,
diff --git a/Tests/WalletConnectSignTests/ApproveEngineTests.swift b/Tests/WalletConnectSignTests/ApproveEngineTests.swift
index 4a4108dba..de84c86d2 100644
--- a/Tests/WalletConnectSignTests/ApproveEngineTests.swift
+++ b/Tests/WalletConnectSignTests/ApproveEngineTests.swift
@@ -18,6 +18,7 @@ final class ApproveEngineTests: XCTestCase {
var sessionStorageMock: WCSessionStorageMock!
var pairingRegisterer: PairingRegistererMock!
var proposalPayloadsStore: CodableStore>!
+ var verifyContextStore: CodableStore!
var sessionTopicToProposal: CodableStore!
var publishers = Set()
@@ -30,10 +31,12 @@ final class ApproveEngineTests: XCTestCase {
sessionStorageMock = WCSessionStorageMock()
pairingRegisterer = PairingRegistererMock()
proposalPayloadsStore = CodableStore>(defaults: RuntimeKeyValueStorage(), identifier: "")
+ verifyContextStore = CodableStore(defaults: RuntimeKeyValueStorage(), identifier: "")
sessionTopicToProposal = CodableStore(defaults: RuntimeKeyValueStorage(), identifier: "")
engine = ApproveEngine(
networkingInteractor: networkingInteractor,
proposalPayloadsStore: proposalPayloadsStore,
+ verifyContextStore: verifyContextStore,
sessionTopicToProposal: sessionTopicToProposal,
pairingRegisterer: pairingRegisterer,
metadata: metadata,
@@ -147,4 +150,69 @@ final class ApproveEngineTests: XCTestCase {
XCTAssertFalse(cryptoMock.hasAgreementSecret(for: session.topic), "Responder must remove agreement secret")
XCTAssertFalse(cryptoMock.hasPrivateKey(for: session.self.publicKey!), "Responder must remove private key")
}
+
+ func testVerifyContextStorageAdd() {
+ let proposalReceivedExpectation = expectation(description: "Wallet expects to receive a proposal")
+
+ let pairing = WCPairing.stub()
+ let topicA = pairing.topic
+ pairingStorageMock.setPairing(pairing)
+ let proposerPubKey = AgreementPrivateKey().publicKey.hexRepresentation
+ let proposal = SessionProposal.stub(proposerPubKey: proposerPubKey)
+
+ engine.onSessionProposal = { _, _ in
+ proposalReceivedExpectation.fulfill()
+ }
+ pairingRegisterer.subject.send(RequestSubscriptionPayload(id: RPCID("id"), topic: topicA, request: proposal, decryptedPayload: Data(), publishedAt: Date(), derivedTopic: nil))
+
+ wait(for: [proposalReceivedExpectation], timeout: 0.1)
+
+ XCTAssertTrue(verifyContextStore.getAll().count == 1)
+ }
+
+ func testVerifyContextStorageRemoveOnApprove() async throws {
+ let proposalReceivedExpectation = expectation(description: "Wallet expects to receive a proposal")
+
+ let pairing = WCPairing.stub()
+ let topicA = pairing.topic
+ pairingStorageMock.setPairing(pairing)
+ let proposerPubKey = AgreementPrivateKey().publicKey.hexRepresentation
+ let proposal = SessionProposal.stub(proposerPubKey: proposerPubKey)
+
+ engine.onSessionProposal = { _, _ in
+ proposalReceivedExpectation.fulfill()
+ }
+ pairingRegisterer.subject.send(RequestSubscriptionPayload(id: RPCID("id"), topic: topicA, request: proposal, decryptedPayload: Data(), publishedAt: Date(), derivedTopic: nil))
+
+ wait(for: [proposalReceivedExpectation], timeout: 0.1)
+
+ XCTAssertTrue(verifyContextStore.getAll().count == 1)
+
+ try await engine.approveProposal(proposerPubKey: proposal.proposer.publicKey, validating: SessionNamespace.stubDictionary())
+
+ XCTAssertTrue(verifyContextStore.getAll().isEmpty)
+ }
+
+ func testVerifyContextStorageRemoveOnReject() async throws {
+ let proposalReceivedExpectation = expectation(description: "Wallet expects to receive a proposal")
+
+ let pairing = WCPairing.stub()
+ let topicA = pairing.topic
+ pairingStorageMock.setPairing(pairing)
+ let proposerPubKey = AgreementPrivateKey().publicKey.hexRepresentation
+ let proposal = SessionProposal.stub(proposerPubKey: proposerPubKey)
+
+ engine.onSessionProposal = { _, _ in
+ proposalReceivedExpectation.fulfill()
+ }
+ pairingRegisterer.subject.send(RequestSubscriptionPayload(id: RPCID("id"), topic: topicA, request: proposal, decryptedPayload: Data(), publishedAt: Date(), derivedTopic: nil))
+
+ wait(for: [proposalReceivedExpectation], timeout: 0.1)
+
+ XCTAssertTrue(verifyContextStore.getAll().count == 1)
+
+ try await engine.reject(proposerPubKey: proposal.proposer.publicKey, reason: .userRejected)
+
+ XCTAssertTrue(verifyContextStore.getAll().isEmpty)
+ }
}
diff --git a/Tests/WalletConnectSignTests/SessionEngineTests.swift b/Tests/WalletConnectSignTests/SessionEngineTests.swift
index c37a9b98b..23f1e420b 100644
--- a/Tests/WalletConnectSignTests/SessionEngineTests.swift
+++ b/Tests/WalletConnectSignTests/SessionEngineTests.swift
@@ -7,11 +7,15 @@ final class SessionEngineTests: XCTestCase {
var networkingInteractor: NetworkingInteractorMock!
var sessionStorage: WCSessionStorageMock!
+ var proposalPayloadsStore: CodableStore>!
+ var verifyContextStore: CodableStore!
var engine: SessionEngine!
override func setUp() {
networkingInteractor = NetworkingInteractorMock()
sessionStorage = WCSessionStorageMock()
+ proposalPayloadsStore = CodableStore>(defaults: RuntimeKeyValueStorage(), identifier: "")
+ verifyContextStore = CodableStore(defaults: RuntimeKeyValueStorage(), identifier: "")
engine = SessionEngine(
networkingInteractor: networkingInteractor,
historyService: HistoryService(
@@ -20,8 +24,11 @@ final class SessionEngineTests: XCTestCase {
defaults: RuntimeKeyValueStorage(),
identifier: ""
)
- )
+ ),
+ proposalPayloadsStore: proposalPayloadsStore,
+ verifyContextStore: verifyContextStore
),
+ verifyContextStore: verifyContextStore,
verifyClient: VerifyClientMock(),
kms: KeyManagementServiceMock(),
sessionStore: sessionStorage,
diff --git a/Tests/WalletConnectUtilsTests/KeyedDatabaseTests.swift b/Tests/WalletConnectUtilsTests/KeyedDatabaseTests.swift
new file mode 100644
index 000000000..1cd08b53f
--- /dev/null
+++ b/Tests/WalletConnectUtilsTests/KeyedDatabaseTests.swift
@@ -0,0 +1,50 @@
+import XCTest
+import JSONRPC
+import TestingUtils
+@testable import WalletConnectUtils
+
+final class KeyedDatabaseTests: XCTestCase {
+
+ struct Object: DatabaseObject {
+ let key: String
+ let value: String
+
+ var databaseId: String {
+ return key
+ }
+ }
+
+ let storageKey: String = "storageKey"
+
+ var sut: KeyedDatabase