Skip to content

Commit

Permalink
Handle request in queue
Browse files Browse the repository at this point in the history
  • Loading branch information
wuyuehyang committed Apr 10, 2024
1 parent 57806b0 commit 62043ef
Show file tree
Hide file tree
Showing 12 changed files with 183 additions and 107 deletions.
4 changes: 4 additions & 0 deletions Mixin.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -929,6 +929,7 @@
94E619B92645BAA000A38049 /* SuspiciousLinkView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 94E619B82645BAA000A38049 /* SuspiciousLinkView.xib */; };
94E82DBC2BB54A1C00092249 /* LoadingIndicatorTableFooterView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 94E82DBB2BB54A1C00092249 /* LoadingIndicatorTableFooterView.xib */; };
94E8913925C019F000F1E5D4 /* Pods-Mixin-acknowledgements.plist in Resources */ = {isa = PBXBuildFile; fileRef = 94E8913825C019F000F1E5D4 /* Pods-Mixin-acknowledgements.plist */; };
94E9C0962BC6978F00D6157C /* Web3PopupCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 94E9C0952BC6978F00D6157C /* Web3PopupCoordinator.swift */; };
94ECCCAB261A029F004E9E2A /* Window.swift in Sources */ = {isa = PBXBuildFile; fileRef = 94ECCCAA261A029F004E9E2A /* Window.swift */; };
94ED08B02B5FAFC900493312 /* AppUserSearchResult.swift in Sources */ = {isa = PBXBuildFile; fileRef = 94ED08AF2B5FAFC900493312 /* AppUserSearchResult.swift */; };
94ED08B62B5FB1C300493312 /* ExploreActionCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 94ED08B42B5FB1C300493312 /* ExploreActionCell.swift */; };
Expand Down Expand Up @@ -2127,6 +2128,7 @@
94E619B82645BAA000A38049 /* SuspiciousLinkView.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = SuspiciousLinkView.xib; sourceTree = "<group>"; };
94E82DBB2BB54A1C00092249 /* LoadingIndicatorTableFooterView.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = LoadingIndicatorTableFooterView.xib; sourceTree = "<group>"; };
94E8913825C019F000F1E5D4 /* Pods-Mixin-acknowledgements.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; name = "Pods-Mixin-acknowledgements.plist"; path = "Pods/Target Support Files/Pods-Mixin/Pods-Mixin-acknowledgements.plist"; sourceTree = SOURCE_ROOT; };
94E9C0952BC6978F00D6157C /* Web3PopupCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Web3PopupCoordinator.swift; sourceTree = "<group>"; };
94ECCCAA261A029F004E9E2A /* Window.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Window.swift; sourceTree = "<group>"; };
94ED08AF2B5FAFC900493312 /* AppUserSearchResult.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppUserSearchResult.swift; sourceTree = "<group>"; };
94ED08B42B5FB1C300493312 /* ExploreActionCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExploreActionCell.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -3108,6 +3110,7 @@
942F4728299CDF1800528759 /* URLSessionWebSocketFactory.swift */,
942F472A299CDF8100528759 /* Web3SignerFactory.swift */,
94C0D7E829AD348500E372FC /* InPlaceKeyStorage.swift */,
94E9C0952BC6978F00D6157C /* Web3PopupCoordinator.swift */,
94E35CA2298B836300ADB40D /* WalletConnectService.swift */,
940764C229B2633C00B779A6 /* WalletConnectDecodedSigningRequest.swift */,
947C5E5A29B0FE9B00838458 /* WalletConnectSession.swift */,
Expand Down Expand Up @@ -5431,6 +5434,7 @@
7B5A748F230274E200C6107E /* GroupIconMaker.swift in Sources */,
DFC0ED0923BB7D990091E7AC /* GiphyImage.swift in Sources */,
7CEB735E29DBC44A006FB5B2 /* DeviceTransferParticipant.swift in Sources */,
94E9C0962BC6978F00D6157C /* Web3PopupCoordinator.swift in Sources */,
7CEB735C29DBB737006FB5B2 /* DeviceTransferConversation.swift in Sources */,
7B0B01921FEA19BA000EEE4F /* DecryptionFailedMessageViewModel.swift in Sources */,
7B2E3E4B1FA07F4500DDDDEB /* SelectCountryViewController.swift in Sources */,
Expand Down
1 change: 1 addition & 0 deletions Mixin/AppDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -241,6 +241,7 @@ extension AppDelegate {
WKWebsiteDataStore.default().removeAuthenticationRelatedData()
BackupJobQueue.shared.cancelAllOperations()
WalletConnectService.shared.disconnectAllSessions()
Web3PopupCoordinator.rejectAllPopups()

UIApplication.shared.setShortcutItemsEnabled(false)
UIApplication.shared.applicationIconBadgeNumber = 1
Expand Down
86 changes: 17 additions & 69 deletions Mixin/Service/Web3/WalletConnectService.swift
Original file line number Diff line number Diff line change
Expand Up @@ -33,10 +33,6 @@ final class WalletConnectService {

private var subscribes = Set<AnyCancellable>()

// Only one request or proposal can be presented at a time
// New incoming requests will be rejected if `presentedViewController` is not nil
private weak var presentedViewController: UIViewController?

private init() {
Networking.configure(groupIdentifier: appGroupIdentifier,
projectId: MixinKeys.walletConnect,
Expand Down Expand Up @@ -109,40 +105,6 @@ final class WalletConnectService {
}
}

func presentRequest(viewController: UIViewController) {
guard let container = UIApplication.homeContainerViewController else {
return
}
let hasPendingRequest: Bool
if let presentedViewController {
// FIXME: Workaround for the issue that requests can't be processed. In some cases, even after the previous
// request has been dismissed, the presentedViewController remains non-nil, preventing the handling of
// subsequent requests. There might be a retain cycle somewhere inside the view controller.
hasPendingRequest = presentedViewController.presentingViewController != nil
if hasPendingRequest {
Logger.web3.warn(category: "Service", message: "Previous request not released")
}
} else {
hasPendingRequest = false
}
guard !hasPendingRequest else {
presentRejection(title: R.string.localizable.request_rejected(),
message: R.string.localizable.request_rejected_reason_another_request_in_process())
return
}
container.presentOnTopMostPresentedController(viewController, animated: true)
presentedViewController = viewController
}

func presentRejection(title: String, message: String) {
guard let container = UIApplication.homeContainerViewController else {
return
}
let alert = UIAlertController(title: title, message: message, preferredStyle: .alert)
alert.addAction(UIAlertAction(title: R.string.localizable.ok(), style: .cancel))
container.presentOnTopMostPresentedController(alert, animated: true)
}

private func reloadSessions(sessions: [WalletConnectSign.Session]) {
self.sessions = sessions.map(WalletConnectSession.init(session:))
let topics = self.sessions.map(\.topic)
Expand Down Expand Up @@ -300,8 +262,9 @@ extension WalletConnectService {
requiredNamespaces = requiredChains.joined(separator: ", ")
}
DispatchQueue.main.async {
self.presentRejection(title: "Chain not supported",
message: "\(proposal.proposer.name) requires to support \(requiredNamespaces)")
let title = "Chain not supported"
let message = "\(proposal.proposer.name) requires to support \(requiredNamespaces)"
Web3PopupCoordinator.enqueue(popup: .rejection(title: title, message: message))
}
Task {
try await Web3Wallet.instance.rejectSession(proposalId: proposal.id, reason: .unsupportedChains)
Expand All @@ -315,8 +278,9 @@ extension WalletConnectService {
logger.warn(category: "Service", message: "Requires to support \(proposalEvents)")
let events = proposalEvents.joined(separator: ", ")
DispatchQueue.main.async {
self.presentRejection(title: "Chain not supported",
message: "\(proposal.proposer.name) requires to support \(events))")
let title = "Chain not supported"
let message = "\(proposal.proposer.name) requires to support \(events)"
Web3PopupCoordinator.enqueue(popup: .rejection(title: title, message: message))
}
Task {
try await Web3Wallet.instance.rejectSession(proposalId: proposal.id, reason: .upsupportedEvents)
Expand All @@ -325,32 +289,15 @@ extension WalletConnectService {
}

let account: String? = PropertiesDAO.shared.value(forKey: .evmAddress)
if account == nil {
DispatchQueue.main.async {
let unlock = UnlockWeb3WalletViewController(chain: chains[0])
unlock.onDismiss = { isUnlocked in
if isUnlocked {
self.presentedViewController = nil // Value may not released immediately
let connectWallet = ConnectWalletViewController(proposal: proposal,
chains: chains.map(\.caip2),
events: Array(events))
self.presentRequest(viewController: connectWallet)
} else {
Task {
try await Web3Wallet.instance.rejectSession(proposalId: proposal.id, reason: .userRejected)
}
}
}
self.presentRequest(viewController: unlock)
}
} else {
logger.info(category: "Service", message: "Showing: \(proposal))")
DispatchQueue.main.async {
let connectWallet = ConnectWalletViewController(proposal: proposal,
chains: chains.map(\.caip2),
events: Array(events))
self.presentRequest(viewController: connectWallet)
DispatchQueue.main.async {
if account == nil {
let controller = UnlockWeb3WalletViewController(chain: chains[0])
Web3PopupCoordinator.enqueue(popup: .unlock(controller))
}
let connectWallet = ConnectWalletViewController(proposal: proposal,
chains: chains.map(\.caip2),
events: Array(events))
Web3PopupCoordinator.enqueue(popup: .request(connectWallet))
}
}
}
Expand All @@ -366,8 +313,9 @@ extension WalletConnectService {
let error = JSONRPCError(code: -1, message: "Missing session")
try await Web3Wallet.instance.respond(topic: topic, requestId: request.id, response: .error(error))
}
self.presentRejection(title: R.string.localizable.request_rejected(),
message: R.string.localizable.session_not_found())
let title = R.string.localizable.request_rejected()
let message = R.string.localizable.session_not_found()
Web3PopupCoordinator.enqueue(popup: .rejection(title: title, message: message))
}
}
}
Expand Down
26 changes: 15 additions & 11 deletions Mixin/Service/Web3/WalletConnectSession.swift
Original file line number Diff line number Diff line change
Expand Up @@ -65,8 +65,9 @@ final class WalletConnectSession {
case .ethSignTypedData, .ethSignTypedDataV4:
requestETHSignTypedData(with: request)
case .ethSignTransaction:
WalletConnectService.shared.presentRejection(title: R.string.localizable.request_rejected(),
message: R.string.localizable.method_not_supported(request.method))
let title = R.string.localizable.request_rejected()
let message = R.string.localizable.method_not_supported(request.method)
Web3PopupCoordinator.enqueue(popup: .rejection(title: title, message: message))
Logger.web3.warn(category: "Session", message: "eth_signTransaction rejected")
Task {
try await Web3Wallet.instance.respond(topic: request.topic,
Expand All @@ -76,8 +77,9 @@ final class WalletConnectSession {
case .ethSendTransaction:
requestSendTransaction(with: request)
case .none:
WalletConnectService.shared.presentRejection(title: R.string.localizable.request_rejected(),
message: R.string.localizable.method_not_supported(request.method))
let title = R.string.localizable.request_rejected()
let message = R.string.localizable.method_not_supported(request.method)
Web3PopupCoordinator.enqueue(popup: .rejection(title: title, message: message))
Logger.web3.warn(category: "Session", message: "Unknown method: \(request.method)")
Task {
try await Web3Wallet.instance.respond(topic: request.topic,
Expand All @@ -103,8 +105,9 @@ extension WalletConnectSession {
Task {
try await Web3Wallet.instance.respond(topic: request.topic, requestId: request.id, response: .error(.methodNotFound))
}
WalletConnectService.shared.presentRejection(title: R.string.localizable.request_rejected(),
message: R.string.localizable.method_not_supported(request.method))
let title = R.string.localizable.request_rejected()
let message = R.string.localizable.method_not_supported(request.method)
Web3PopupCoordinator.enqueue(popup: .rejection(title: title, message: message))
}

private func requestETHSignTypedData(with request: Request) {
Expand Down Expand Up @@ -147,13 +150,14 @@ extension WalletConnectSession {
transaction: transactionPreview,
chain: chain,
chainToken: chainToken)
WalletConnectService.shared.presentRequest(viewController: transactionRequest)
Web3PopupCoordinator.enqueue(popup: .request(transactionRequest))
}
} catch {
Logger.web3.error(category: "Session", message: "Failed to request tx: \(error)")
DispatchQueue.main.async {
WalletConnectService.shared.presentRejection(title: R.string.localizable.request_rejected(),
message: R.string.localizable.unable_to_decode_the_request(error.localizedDescription))
let title = R.string.localizable.request_rejected()
let message = R.string.localizable.unable_to_decode_the_request(error.localizedDescription)
Web3PopupCoordinator.enqueue(popup: .rejection(title: title, message: message))
}
Task {
let error = JSONRPCError(code: 0, message: "Local failed")
Expand All @@ -175,12 +179,12 @@ extension WalletConnectSession {
throw Error.noAccount
}
let signRequest = SignRequestViewController(address: address, session: self, request: decoded)
WalletConnectService.shared.presentRequest(viewController: signRequest)
Web3PopupCoordinator.enqueue(popup: .request(signRequest))
} catch {
Logger.web3.error(category: "Session", message: "Failed to sign: \(error)")
let title = R.string.localizable.request_rejected()
let message = R.string.localizable.unable_to_decode_the_request(error.localizedDescription)
WalletConnectService.shared.presentRejection(title: title, message: message)
Web3PopupCoordinator.enqueue(popup: .rejection(title: title, message: message))
Task {
let error = JSONRPCError(code: 0, message: error.localizedDescription)
try await Web3Wallet.instance.respond(topic: request.topic, requestId: request.id, response: .error(error))
Expand Down
89 changes: 89 additions & 0 deletions Mixin/Service/Web3/Web3PopupCoordinator.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
import UIKit

protocol Web3PopupViewController: UIViewController {

var onDismiss: (() -> Void)? { get set }

func reject()

}

struct Web3PopupCoordinator {

private class AlertController: UIAlertController {

var onDismiss: (() -> Void)?

override func viewDidDisappear(_ animated: Bool) {
super.viewDidDisappear(animated)
onDismiss?()
}

}

enum Popup {
case unlock(UnlockWeb3WalletViewController)
case request(Web3PopupViewController)
case rejection(title: String, message: String)
}

private static var popups: [Popup] = []

static func enqueue(popup: Popup) {
popups.append(popup)
if popups.count == 1 {
presentNextPopupIfNeeded()
}
}

static func rejectAllPopups() {
for popup in popups {
switch popup {
case .request(let controller):
controller.reject()
case .unlock, .rejection:
break
}
}
popups = []
}

private static func presentNextPopupIfNeeded() {
guard let container = UIApplication.homeContainerViewController else {
return
}
guard let popup = popups.first else {
return
}
let viewController: UIViewController
switch popup {
case .unlock(let controller):
// TODO: Tell user subsequent request will fail if not approved
controller.onDismiss = {
popups.removeFirst()
if controller.isUnlocked {
presentNextPopupIfNeeded()
} else {
rejectAllPopups()
}
}
viewController = controller
case .request(let controller):
controller.onDismiss = {
popups.removeFirst()
presentNextPopupIfNeeded()
}
viewController = controller
case .rejection(let title, let message):
let alert = AlertController(title: title, message: message, preferredStyle: .alert)
alert.addAction(UIAlertAction(title: R.string.localizable.ok(), style: .cancel))
alert.onDismiss = {
popups.removeFirst()
presentNextPopupIfNeeded()
}
viewController = alert
}
container.presentOnTopMostPresentedController(viewController, animated: true)
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ final class Web3WalletViewController: UIViewController {
selector: #selector(propertiesDidUpdate(_:)),
name: PropertiesDAO.propertyDidUpdateNotification,
object: nil)
address = PropertiesDAO.shared.value(forKey: .evmAddress)
address = PropertiesDAO.shared.unsafeValue(forKey: .evmAddress)
if let address {
tableHeaderView.showCopyAddress(chain: chain, address: address)
} else {
Expand Down Expand Up @@ -84,6 +84,12 @@ final class Web3WalletViewController: UIViewController {
}

private func reloadDapps(web3Chains: [String: Web3Chain]) {
#if DEBUG
if let dapps = web3Chains[chain.internalID]?.dapps {
self.dapps = dapps
tableView.reloadData()
}
#endif
tableView.tableFooterView = nil
}

Expand Down
Loading

0 comments on commit 62043ef

Please sign in to comment.