Skip to content

Commit

Permalink
Merge pull request #1295 from WalletConnect/requests-queue
Browse files Browse the repository at this point in the history
Requests queue
  • Loading branch information
llbartekll authored Jan 31, 2024
2 parents c2d4c55 + bf777cd commit e90cad3
Show file tree
Hide file tree
Showing 13 changed files with 117 additions and 26 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -73,14 +73,23 @@
allowLocationSimulation = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "C56EE21A293F55ED004840D1"
BuildableName = "WalletApp.app"
BlueprintName = "WalletApp"
ReferencedContainer = "container:ExampleApp.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "764E1D3B26F8D3FC00A1FB15"
BuildableName = "WalletConnect Wallet.app"
BlueprintName = "Wallet"
ReferencedContainer = "container:ExampleApp.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</MacroExpansion>
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
Expand All @@ -98,6 +107,15 @@
ReferencedContainer = "container:ExampleApp.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "764E1D3B26F8D3FC00A1FB15"
BuildableName = "WalletConnect Wallet.app"
BlueprintName = "Wallet"
ReferencedContainer = "container:ExampleApp.xcodeproj">
</BuildableReference>
</MacroExpansion>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import UIKit
import Combine
import SwiftUI

final class MainPresenter {
private let interactor: MainInteractor
Expand Down Expand Up @@ -48,10 +49,15 @@ extension MainPresenter {

interactor.sessionRequestPublisher
.receive(on: DispatchQueue.main)
.sink { [unowned self] request, context in
.sink { [unowned self] (request, context) in
guard let vc = UIApplication.currentWindow.rootViewController?.topController,
vc.restorationIdentifier != SessionRequestModule.restorationIdentifier else {
return
}
router.dismiss()
router.present(sessionRequest: request, importAccount: importAccount, sessionContext: context)
}.store(in: &disposeBag)


interactor.requestPublisher
.receive(on: DispatchQueue.main)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,15 @@ import SwiftUI
import Web3Wallet

final class SessionRequestModule {
static let restorationIdentifier = "SessionRequestViewController"
@discardableResult
static func create(app: Application, sessionRequest: Request, importAccount: ImportAccount, sessionContext: VerifyContext?) -> UIViewController {
let router = SessionRequestRouter(app: app)
let interactor = SessionRequestInteractor()
let presenter = SessionRequestPresenter(interactor: interactor, router: router, sessionRequest: sessionRequest, importAccount: importAccount, context: sessionContext)
let view = SessionRequestView().environmentObject(presenter)
let viewController = SceneViewController(viewModel: presenter, content: view)
viewController.restorationIdentifier = Self.restorationIdentifier

router.viewController = viewController

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import UIKit
import Combine
import WalletConnectNetworking
import Web3Wallet

final class SettingsPresenter: ObservableObject {

Expand Down Expand Up @@ -46,6 +47,7 @@ final class SettingsPresenter: ObservableObject {
guard let account = accountStorage.importAccount?.account else { return }
try await interactor.notifyUnregister(account: account)
accountStorage.importAccount = nil
try await Web3Wallet.instance.cleanup()
UserDefaults.standard.set(nil, forKey: "deviceToken")
await router.presentWelcome()
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ final class WalletRouter {

func present(sessionRequest: Request, importAccount: ImportAccount, sessionContext: VerifyContext?) {
SessionRequestModule.create(app: app, sessionRequest: sessionRequest, importAccount: importAccount, sessionContext: sessionContext)
.presentFullScreen(from: viewController, transparentBackground: true)
.presentFullScreen(from: UIApplication.currentWindow.rootViewController!, transparentBackground: true)
}

func present(sessionProposal: Session.Proposal, importAccount: ImportAccount, sessionContext: VerifyContext?) {
Expand Down
25 changes: 20 additions & 5 deletions Sources/WalletConnectSign/Engine/Common/SessionEngine.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,16 @@ final class SessionEngine {
}

var onSessionsUpdate: (([Session]) -> Void)?
var onSessionRequest: ((Request, VerifyContext?) -> Void)?
var onSessionResponse: ((Response) -> Void)?
var onSessionRejected: ((String, SessionType.Reason) -> Void)?
var onSessionDelete: ((String, SessionType.Reason) -> Void)?
var onEventReceived: ((String, Session.Event, Blockchain?) -> Void)?

var sessionRequestPublisher: AnyPublisher<(request: Request, context: VerifyContext?), Never> {
return sessionRequestsProvider.sessionRequestPublisher
}


private let sessionStore: WCSessionStorage
private let networkingInteractor: NetworkInteracting
private let historyService: HistoryService
Expand All @@ -21,6 +25,7 @@ final class SessionEngine {
private let kms: KeyManagementServiceProtocol
private var publishers = [AnyCancellable]()
private let logger: ConsoleLogging
private let sessionRequestsProvider: SessionRequestsProvider

init(
networkingInteractor: NetworkInteracting,
Expand All @@ -29,7 +34,8 @@ final class SessionEngine {
verifyClient: VerifyClientProtocol,
kms: KeyManagementServiceProtocol,
sessionStore: WCSessionStorage,
logger: ConsoleLogging
logger: ConsoleLogging,
sessionRequestsProvider: SessionRequestsProvider
) {
self.networkingInteractor = networkingInteractor
self.historyService = historyService
Expand All @@ -38,12 +44,17 @@ final class SessionEngine {
self.kms = kms
self.sessionStore = sessionStore
self.logger = logger
self.sessionRequestsProvider = sessionRequestsProvider

setupConnectionSubscriptions()
setupRequestSubscriptions()
setupResponseSubscriptions()
setupUpdateSubscriptions()
setupExpirationSubscriptions()
DispatchQueue.main.asyncAfter(deadline: .now() + 1) { [weak self] in
guard let self = self else {return}
sessionRequestsProvider.emitRequestIfPending()
}
}

func hasSession(for topic: String) -> Bool {
Expand Down Expand Up @@ -95,6 +106,10 @@ final class SessionEngine {
protocolMethod: protocolMethod
)
verifyContextStore.delete(forKey: requestId.string)
DispatchQueue.main.asyncAfter(deadline: .now() + 1) { [weak self] in
guard let self = self else {return}
sessionRequestsProvider.emitRequestIfPending()
}
}

func emit(topic: String, event: SessionType.EventParams.Event, chainId: Blockchain) async throws {
Expand Down Expand Up @@ -249,12 +264,12 @@ private extension SessionEngine {
let response = try await verifyClient.verifyOrigin(assertionId: assertionId)
let verifyContext = verifyClient.createVerifyContext(origin: response.origin, domain: session.peerParticipant.metadata.url, isScam: response.isScam)
verifyContextStore.set(verifyContext, forKey: request.id.string)
onSessionRequest?(request, verifyContext)

sessionRequestsProvider.emitRequestIfPending()
} catch {
let verifyContext = verifyClient.createVerifyContext(origin: nil, domain: session.peerParticipant.metadata.url, isScam: nil)
verifyContextStore.set(verifyContext, forKey: request.id.string)
onSessionRequest?(request, verifyContext)
return
sessionRequestsProvider.emitRequestIfPending()
}
}
}
Expand Down
29 changes: 21 additions & 8 deletions Sources/WalletConnectSign/Services/HistoryService.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,32 +15,45 @@ final class HistoryService {

public func getSessionRequest(id: RPCID) -> (request: Request, context: VerifyContext?)? {
guard let record = history.get(recordId: id) else { return nil }
guard let (request, recordId) = mapRequestRecord(record) else {
guard let (request, recordId, _) = mapRequestRecord(record) else {
return nil
}
return (request, try? verifyContextStore.get(key: recordId.string))
}

func getPendingRequests() -> [(request: Request, context: VerifyContext?)] {
getPendingRequestsSortedByTimestamp()
}

func getPendingRequestsSortedByTimestamp() -> [(request: Request, context: VerifyContext?)] {
let requests = history.getPending()
.compactMap { mapRequestRecord($0) }
.filter { !$0.0.isExpired() } // Note the change here to access the Request part of the tuple
return requests.map { (request: $0.0, context: try? verifyContextStore.get(key: $0.1.string)) }
}
.filter { !$0.0.isExpired() }
.sorted {
switch ($0.2, $1.2) {
case let (date1?, date2?): return date1 < date2 // Both dates are present
case (nil, _): return false // First date is nil, so it should go last
case (_, nil): return true // Second date is nil, so the first one should come first
}
}
.map { (request: $0.0, context: try? verifyContextStore.get(key: $0.1.string)) }

return requests
}

func getPendingRequestsWithRecordId() -> [(request: Request, recordId: RPCID)] {
history.getPending()
return history.getPending()
.compactMap { mapRequestRecord($0) }
.map { (request: $0.0, recordId: $0.1) }
}

func getPendingRequests(topic: String) -> [(request: Request, context: VerifyContext?)] {
return getPendingRequests().filter { $0.request.topic == topic }
return getPendingRequestsSortedByTimestamp().filter { $0.request.topic == topic }
}
}

private extension HistoryService {
func mapRequestRecord(_ record: RPCHistory.Record) -> (Request, RPCID)? {
func mapRequestRecord(_ record: RPCHistory.Record) -> (Request, RPCID, Date?)? {
guard let request = try? record.request.params?.get(SessionType.RequestParams.self)
else { return nil }

Expand All @@ -53,6 +66,6 @@ private extension HistoryService {
expiryTimestamp: request.request.expiryTimestamp
)

return (mappedRequest, record.id)
return (mappedRequest, record.id, record.timestamp)
}
}
6 changes: 5 additions & 1 deletion Sources/WalletConnectSign/Services/SignCleanupService.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,16 @@ final class SignCleanupService {
private let kms: KeyManagementServiceProtocol
private let sessionTopicToProposal: CodableStore<Session.Proposal>
private let networkInteractor: NetworkInteracting
private let rpcHistory: RPCHistory

init(pairingStore: WCPairingStorage, sessionStore: WCSessionStorage, kms: KeyManagementServiceProtocol, sessionTopicToProposal: CodableStore<Session.Proposal>, networkInteractor: NetworkInteracting) {
init(pairingStore: WCPairingStorage, sessionStore: WCSessionStorage, kms: KeyManagementServiceProtocol, sessionTopicToProposal: CodableStore<Session.Proposal>, networkInteractor: NetworkInteracting,
rpcHistory: RPCHistory) {
self.pairingStore = pairingStore
self.sessionStore = sessionStore
self.sessionTopicToProposal = sessionTopicToProposal
self.networkInteractor = networkInteractor
self.kms = kms
self.rpcHistory = rpcHistory
}

func cleanup() async throws {
Expand All @@ -39,6 +42,7 @@ private extension SignCleanupService {
pairingStore.deleteAll()
sessionStore.deleteAll()
sessionTopicToProposal.deleteAll()
rpcHistory.deleteAll()
try kms.deleteAll()
}
}
20 changes: 20 additions & 0 deletions Sources/WalletConnectSign/Sign/SessionRequestsProvider.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import Combine
import Foundation

class SessionRequestsProvider {
private let historyService: HistoryService
private var sessionRequestPublisherSubject = PassthroughSubject<(request: Request, context: VerifyContext?), Never>()
public var sessionRequestPublisher: AnyPublisher<(request: Request, context: VerifyContext?), Never> {
sessionRequestPublisherSubject.eraseToAnyPublisher()
}

init(historyService: HistoryService) {
self.historyService = historyService
}

func emitRequestIfPending() {
if let oldestRequest = self.historyService.getPendingRequestsSortedByTimestamp().first {
self.sessionRequestPublisherSubject.send(oldestRequest)
}
}
}
6 changes: 1 addition & 5 deletions Sources/WalletConnectSign/Sign/SignClient.swift
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ public final class SignClient: SignClientProtocol {
///
/// In most cases event will be emited on wallet
public var sessionRequestPublisher: AnyPublisher<(request: Request, context: VerifyContext?), Never> {
sessionRequestPublisherSubject.eraseToAnyPublisher()
sessionEngine.sessionRequestPublisher
}

/// Publisher that sends web socket connection status
Expand Down Expand Up @@ -138,7 +138,6 @@ public final class SignClient: SignClientProtocol {
private let requestsExpiryWatcher: RequestsExpiryWatcher

private let sessionProposalPublisherSubject = PassthroughSubject<(proposal: Session.Proposal, context: VerifyContext?), Never>()
private let sessionRequestPublisherSubject = PassthroughSubject<(request: Request, context: VerifyContext?), Never>()
private let socketConnectionStatusPublisherSubject = PassthroughSubject<SocketConnectionStatus, Never>()
private let sessionSettlePublisherSubject = PassthroughSubject<Session, Never>()
private let sessionDeletePublisherSubject = PassthroughSubject<(String, Reason), Never>()
Expand Down Expand Up @@ -359,9 +358,6 @@ public final class SignClient: SignClientProtocol {
approveEngine.onSessionSettle = { [unowned self] settledSession in
sessionSettlePublisherSubject.send(settledSession)
}
sessionEngine.onSessionRequest = { [unowned self] (sessionRequest, context) in
sessionRequestPublisherSubject.send((sessionRequest, context))
}
sessionEngine.onSessionDelete = { [unowned self] topic, reason in
sessionDeletePublisherSubject.send((topic, reason))
}
Expand Down
5 changes: 3 additions & 2 deletions Sources/WalletConnectSign/Sign/SignClientFactory.swift
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,8 @@ public struct SignClientFactory {
let verifyContextStore = CodableStore<VerifyContext>(defaults: keyValueStorage, identifier: VerifyStorageIdentifiers.context.rawValue)
let historyService = HistoryService(history: rpcHistory, verifyContextStore: verifyContextStore)
let verifyClient = VerifyClientFactory.create()
let sessionEngine = SessionEngine(networkingInteractor: networkingClient, historyService: historyService, verifyContextStore: verifyContextStore, verifyClient: verifyClient, kms: kms, sessionStore: sessionStore, logger: logger)
let sessionRequestsProvider = SessionRequestsProvider(historyService: historyService)
let sessionEngine = SessionEngine(networkingInteractor: networkingClient, historyService: historyService, verifyContextStore: verifyContextStore, verifyClient: verifyClient, kms: kms, sessionStore: sessionStore, logger: logger, sessionRequestsProvider: sessionRequestsProvider)
let nonControllerSessionStateMachine = NonControllerSessionStateMachine(networkingInteractor: networkingClient, kms: kms, sessionStore: sessionStore, logger: logger)
let controllerSessionStateMachine = ControllerSessionStateMachine(networkingInteractor: networkingClient, kms: kms, sessionStore: sessionStore, logger: logger)
let sessionExtendRequester = SessionExtendRequester(sessionStore: sessionStore, networkingInteractor: networkingClient)
Expand All @@ -64,7 +65,7 @@ public struct SignClientFactory {
verifyClient: verifyClient,
rpcHistory: rpcHistory
)
let cleanupService = SignCleanupService(pairingStore: pairingStore, sessionStore: sessionStore, kms: kms, sessionTopicToProposal: sessionTopicToProposal, networkInteractor: networkingClient)
let cleanupService = SignCleanupService(pairingStore: pairingStore, sessionStore: sessionStore, kms: kms, sessionTopicToProposal: sessionTopicToProposal, networkInteractor: networkingClient, rpcHistory: rpcHistory)
let deleteSessionService = DeleteSessionService(networkingInteractor: networkingClient, kms: kms, sessionStore: sessionStore, logger: logger)
let disconnectService = DisconnectService(deleteSessionService: deleteSessionService, sessionStorage: sessionStore)
let sessionPingService = SessionPingService(sessionStorage: sessionStore, networkingInteractor: networkingClient, logger: logger)
Expand Down
6 changes: 5 additions & 1 deletion Sources/WalletConnectUtils/RPCHistory/RPCHistory.swift
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ public final class RPCHistory {
case .responseDuplicateNotAllowed:
return "Response duplicates are not allowed."
case .requestMatchingResponseNotFound:
return "Matching requesr for the response not found."
return "Matching request for the response not found."
}
}
}
Expand Down Expand Up @@ -119,6 +119,10 @@ public final class RPCHistory {
public func getPending() -> [Record] {
storage.getAll().filter { $0.response == nil }
}

public func deleteAll() {
storage.deleteAll()
}
}

extension RPCHistory {
Expand Down
12 changes: 11 additions & 1 deletion Tests/WalletConnectSignTests/SessionEngineTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,17 @@ final class SessionEngineTests: XCTestCase {
verifyClient: VerifyClientMock(),
kms: KeyManagementServiceMock(),
sessionStore: sessionStorage,
logger: ConsoleLoggerMock()
logger: ConsoleLoggerMock(),
sessionRequestsProvider: SessionRequestsProvider(
historyService: HistoryService(
history: RPCHistory(
keyValueStore: .init(
defaults: RuntimeKeyValueStorage(),
identifier: ""
)
),
verifyContextStore: verifyContextStore
))
)
}

Expand Down

0 comments on commit e90cad3

Please sign in to comment.