tokenary/Tokenary macOS/Agent.swift

285 lines
12 KiB
Swift
Raw Normal View History

2021-11-30 15:56:00 +03:00
// Copyright © 2021 Tokenary. All rights reserved.
2021-06-12 19:16:23 +03:00
import Cocoa
import SafariServices
2021-06-13 15:30:19 +03:00
import LocalAuthentication
2022-05-07 23:10:08 +03:00
import WalletCore
2021-06-12 19:16:23 +03:00
class Agent: NSObject {
2021-11-22 13:11:34 +03:00
enum ExternalRequest {
case safari(SafariRequest)
}
2021-06-13 06:30:20 +03:00
static let shared = Agent()
2021-06-12 19:16:23 +03:00
private let walletsManager = WalletsManager.shared
2021-11-22 13:11:34 +03:00
private override init() { super.init() }
2021-06-13 13:13:47 +03:00
private var statusBarItem: NSStatusItem!
2021-11-30 20:46:02 +03:00
private lazy var hasPassword = Keychain.shared.password != nil
private var didEnterPasswordOnStart = false
private var didStartInitialLAEvaluation = false
private var didCompleteInitialLAEvaluation = false
2021-11-22 13:11:34 +03:00
private var initialExternalRequest: ExternalRequest?
var statusBarButtonIsBlocked = false
2021-06-13 06:30:20 +03:00
2021-06-12 19:16:23 +03:00
func start() {
2023-10-17 19:01:35 +03:00
open()
2021-06-28 16:20:08 +03:00
setupStatusBarItem()
2021-06-13 05:45:54 +03:00
}
2021-11-22 13:11:34 +03:00
func showInitialScreen(externalRequest: ExternalRequest?) {
let isEvaluatingInitialLA = didStartInitialLAEvaluation && !didCompleteInitialLAEvaluation
guard !isEvaluatingInitialLA else {
2021-11-22 13:11:34 +03:00
if externalRequest != nil {
initialExternalRequest = externalRequest
}
return
}
2021-06-19 00:38:05 +03:00
guard hasPassword else {
let welcomeViewController = WelcomeViewController.new { [weak self] createdPassword in
guard createdPassword else { return }
self?.didEnterPasswordOnStart = true
self?.didCompleteInitialLAEvaluation = true
2021-06-19 00:38:05 +03:00
self?.hasPassword = true
2021-11-22 13:11:34 +03:00
self?.showInitialScreen(externalRequest: externalRequest)
2021-06-19 00:38:05 +03:00
}
let windowController = Window.showNew(closeOthers: true)
2021-06-19 00:38:05 +03:00
windowController.contentViewController = welcomeViewController
return
}
guard didEnterPasswordOnStart else {
askAuthentication(on: nil, browser: nil, onStart: true, reason: .start) { [weak self] success in
if success {
self?.didEnterPasswordOnStart = true
2021-11-22 13:11:34 +03:00
self?.showInitialScreen(externalRequest: externalRequest)
}
}
return
}
2021-11-22 13:11:34 +03:00
let request = externalRequest ?? initialExternalRequest
initialExternalRequest = nil
if case let .safari(request) = request {
processSafariRequest(request)
} else {
2021-11-22 13:11:34 +03:00
let accountsList = instantiate(AccountsListViewController.self)
let windowController = Window.showNew(closeOthers: accountsList.selectAccountAction == nil)
2021-11-22 13:11:34 +03:00
windowController.contentViewController = accountsList
}
2021-06-12 19:16:23 +03:00
}
func showApprove(windowController: NSWindowController, browser: Browser?, transaction: Transaction, account: Account, chain: EthereumNetwork, peerMeta: PeerMeta?, completion: @escaping (Transaction?) -> Void) {
let window = windowController.window
let approveViewController = ApproveTransactionViewController.with(transaction: transaction, chain: chain, account: account, peerMeta: peerMeta) { [weak self, weak window] transaction in
if transaction != nil {
self?.askAuthentication(on: window, browser: browser, onStart: false, reason: .sendTransaction) { success in
completion(success ? transaction : nil)
}
} else {
completion(nil)
}
}
windowController.contentViewController = approveViewController
}
func showApprove(windowController: NSWindowController, browser: Browser?, subject: ApprovalSubject, meta: String, peerMeta: PeerMeta?, completion: @escaping (Bool) -> Void) {
let window = windowController.window
let approveViewController = ApproveViewController.with(subject: subject, meta: meta, peerMeta: peerMeta) { [weak self, weak window] result in
2021-06-13 15:30:19 +03:00
if result {
self?.askAuthentication(on: window, getBackTo: window?.contentViewController, browser: browser, onStart: false, reason: subject.asAuthenticationReason) { success in
completion(success)
(window?.contentViewController as? ApproveViewController)?.enableWaiting()
}
2021-06-13 15:30:19 +03:00
} else {
completion(result)
}
2021-06-13 09:32:07 +03:00
}
windowController.contentViewController = approveViewController
}
lazy private var statusBarMenu: NSMenu = {
2021-11-30 15:56:00 +03:00
let menu = NSMenu(title: Strings.tokenary)
2021-11-30 15:56:00 +03:00
let showItem = NSMenuItem(title: Strings.showTokenary, action: #selector(didSelectShowMenuItem), keyEquivalent: "")
2021-12-09 17:42:45 +03:00
let safariItem = NSMenuItem(title: Strings.enableSafariExtension.withEllipsis, action: #selector(enableSafariExtension), keyEquivalent: "")
let mailItem = NSMenuItem(title: Strings.dropUsALine.withEllipsis, action: #selector(didSelectMailMenuItem), keyEquivalent: "")
let githubItem = NSMenuItem(title: Strings.viewOnGithub.withEllipsis, action: #selector(didSelectGitHubMenuItem), keyEquivalent: "")
2023-10-29 18:24:58 +03:00
let xItem = NSMenuItem(title: Strings.viewOnX.withEllipsis, action: #selector(didSelectXMenuItem), keyEquivalent: "")
2021-08-29 21:56:32 +03:00
let quitItem = NSMenuItem(title: Strings.quit, action: #selector(didSelectQuitMenuItem), keyEquivalent: "q")
2021-11-30 15:56:00 +03:00
showItem.attributedTitle = NSAttributedString(string: "👀 " + Strings.showTokenary, attributes: [.font: NSFont.systemFont(ofSize: 15, weight: .semibold)])
showItem.target = self
safariItem.target = self
2021-07-06 19:59:45 +03:00
githubItem.target = self
2023-10-29 18:24:58 +03:00
xItem.target = self
2021-07-06 19:59:45 +03:00
mailItem.target = self
quitItem.target = self
2021-07-06 19:59:45 +03:00
menu.delegate = self
menu.addItem(showItem)
2021-07-06 19:59:45 +03:00
menu.addItem(NSMenuItem.separator())
menu.addItem(safariItem)
2021-07-17 19:05:59 +03:00
menu.addItem(NSMenuItem.separator())
2023-10-29 18:24:58 +03:00
menu.addItem(xItem)
2021-07-06 19:59:45 +03:00
menu.addItem(githubItem)
menu.addItem(mailItem)
menu.addItem(NSMenuItem.separator())
menu.addItem(quitItem)
return menu
}()
2021-06-23 22:21:23 +03:00
func warnBeforeQuitting(updateStatusBarAfterwards: Bool = false) {
Window.activateWindow(nil)
let alert = Alert()
2021-11-30 15:56:00 +03:00
alert.messageText = Strings.quitTokenary
alert.alertStyle = .warning
2021-08-26 21:17:23 +03:00
alert.addButton(withTitle: Strings.ok)
alert.addButton(withTitle: Strings.cancel)
DispatchQueue.main.async { [weak self] in
if alert.runModal() == .alertFirstButtonReturn {
NSApp.terminate(nil)
}
if updateStatusBarAfterwards {
self?.setupStatusBarItem()
}
2021-06-23 22:21:23 +03:00
}
}
2023-10-29 18:24:58 +03:00
@objc private func didSelectXMenuItem() {
NSWorkspace.shared.open(URL.x)
2021-07-30 02:47:14 +03:00
}
2021-07-06 19:59:45 +03:00
@objc private func didSelectGitHubMenuItem() {
2021-12-09 17:42:45 +03:00
NSWorkspace.shared.open(URL.github)
2021-07-06 19:59:45 +03:00
}
@objc func enableSafariExtension() {
SFSafariApplication.showPreferencesForExtension(withIdentifier: Identifiers.safariExtensionBundle)
2021-07-17 19:05:59 +03:00
}
2021-07-06 19:59:45 +03:00
@objc private func didSelectMailMenuItem() {
2021-12-09 17:42:45 +03:00
NSWorkspace.shared.open(URL.email)
2021-07-06 19:59:45 +03:00
}
@objc private func didSelectShowMenuItem() {
2023-10-17 19:01:35 +03:00
open()
}
2021-06-23 21:28:52 +03:00
@objc private func didSelectQuitMenuItem() {
warnBeforeQuitting()
}
func setupStatusBarItem() {
let statusBar = NSStatusBar.system
statusBarItem = statusBar.statusItem(withLength: NSStatusItem.squareLength)
2022-05-07 23:10:08 +03:00
statusBarItem.button?.image = Images.statusBarIcon
statusBarItem.button?.target = self
statusBarItem.button?.action = #selector(statusBarButtonClicked(sender:))
statusBarItem.button?.sendAction(on: [.leftMouseUp, .rightMouseUp])
}
@objc private func statusBarButtonClicked(sender: NSStatusBarButton) {
guard !statusBarButtonIsBlocked, let event = NSApp.currentEvent, event.type == .rightMouseUp || event.type == .leftMouseUp else { return }
2023-10-17 19:01:35 +03:00
statusBarItem.menu = statusBarMenu
statusBarItem.button?.performClick(nil)
2021-06-17 23:11:03 +03:00
}
2023-10-17 19:01:35 +03:00
func open() {
showInitialScreen(externalRequest: .none)
}
func askAuthentication(on: NSWindow?, getBackTo: NSViewController? = nil, browser: Browser?, onStart: Bool, reason: AuthenticationReason, completion: @escaping (Bool) -> Void) {
2021-06-13 15:30:19 +03:00
let context = LAContext()
var error: NSError?
let policy = LAPolicy.deviceOwnerAuthenticationWithBiometrics
let canDoLocalAuthentication = context.canEvaluatePolicy(policy, error: &error)
func showPasswordScreen() {
let window = on ?? Window.showNew(closeOthers: onStart).window
let passwordViewController = PasswordViewController.with(mode: .enter, reason: reason) { [weak window] success in
if let getBackTo = getBackTo {
window?.contentViewController = getBackTo
} else if let browser = browser {
Window.closeWindowAndActivateNext(idToClose: window?.windowNumber, specificBrowser: browser)
} else {
Window.closeWindow(idToClose: window?.windowNumber)
}
2021-06-13 15:30:19 +03:00
completion(success)
}
window?.contentViewController = passwordViewController
}
if canDoLocalAuthentication {
2021-08-26 21:17:23 +03:00
context.localizedCancelTitle = Strings.cancel
didStartInitialLAEvaluation = true
context.evaluatePolicy(.deviceOwnerAuthentication, localizedReason: reason.title) { [weak self] success, _ in
DispatchQueue.main.async {
self?.didCompleteInitialLAEvaluation = true
if !success, onStart, self?.didEnterPasswordOnStart == false {
showPasswordScreen()
}
completion(success)
}
}
} else {
showPasswordScreen()
2021-06-13 15:30:19 +03:00
}
}
2021-11-22 13:11:34 +03:00
private func processSafariRequest(_ safariRequest: SafariRequest) {
var windowNumber: Int?
let action = DappRequestProcessor.processSafariRequest(safariRequest) {
Window.closeWindowAndActivateNext(idToClose: windowNumber, specificBrowser: .safari)
2021-12-14 15:26:00 +03:00
}
switch action {
case .none:
break
case .selectAccount(let accountAction), .switchAccount(let accountAction):
let closeOtherWindows: Bool
if case .selectAccount = action {
closeOtherWindows = false
} else {
closeOtherWindows = true
}
let windowController = Window.showNew(closeOthers: closeOtherWindows)
windowNumber = windowController.window?.windowNumber
2021-11-22 13:11:34 +03:00
let accountsList = instantiate(AccountsListViewController.self)
accountsList.selectAccountAction = accountAction
2021-11-22 13:11:34 +03:00
windowController.contentViewController = accountsList
case .approveMessage(let action):
let windowController = Window.showNew(closeOthers: false)
windowNumber = windowController.window?.windowNumber
showApprove(windowController: windowController, browser: .safari, subject: action.subject, meta: action.meta, peerMeta: action.peerMeta, completion: action.completion)
case .approveTransaction(let action):
let windowController = Window.showNew(closeOthers: false)
windowNumber = windowController.window?.windowNumber
showApprove(windowController: windowController, browser: .safari, transaction: action.transaction, account: action.account, chain: action.chain, peerMeta: action.peerMeta, completion: action.completion)
case .justShowApp:
let windowController = Window.showNew(closeOthers: true)
windowNumber = windowController.window?.windowNumber
let accountsList = instantiate(AccountsListViewController.self)
windowController.contentViewController = accountsList
2021-11-22 13:11:34 +03:00
}
}
2021-06-12 19:16:23 +03:00
}
extension Agent: NSMenuDelegate {
func menuDidClose(_ menu: NSMenu) {
statusBarItem.menu = nil
}
}