2021-06-12 19:16:23 +03:00
|
|
|
// Copyright © 2021 Encrypted Ink. All rights reserved.
|
|
|
|
|
|
|
|
import Cocoa
|
2021-06-13 13:13:47 +03:00
|
|
|
import WalletConnect
|
2021-06-13 15:30:19 +03:00
|
|
|
import LocalAuthentication
|
2021-06-12 19:16:23 +03:00
|
|
|
|
2021-06-19 14:07:57 +03:00
|
|
|
class Agent: NSObject {
|
2021-06-13 05:45:54 +03:00
|
|
|
|
2021-06-13 06:30:20 +03:00
|
|
|
static let shared = Agent()
|
2021-06-13 13:53:38 +03:00
|
|
|
private lazy var statusImage = NSImage(named: "Status")
|
2021-06-12 19:16:23 +03:00
|
|
|
|
2021-06-19 14:07:57 +03:00
|
|
|
private override init() { super.init() }
|
2021-06-13 13:13:47 +03:00
|
|
|
private var statusBarItem: NSStatusItem!
|
2021-06-19 00:38:05 +03:00
|
|
|
private var hasPassword = Keychain.password != nil
|
2021-06-19 19:22:18 +03:00
|
|
|
private var didEnterPasswordOnStart = false
|
2021-06-23 22:15:56 +03:00
|
|
|
var statusBarButtonIsBlocked = false
|
2021-06-13 06:30:20 +03:00
|
|
|
|
2021-06-12 19:16:23 +03:00
|
|
|
func start() {
|
2021-06-18 00:01:51 +03:00
|
|
|
checkPasteboardAndOpen(onAppStart: true)
|
2021-06-13 05:45:54 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
func reopen() {
|
2021-06-18 00:01:51 +03:00
|
|
|
checkPasteboardAndOpen(onAppStart: false)
|
2021-06-13 05:45:54 +03:00
|
|
|
}
|
|
|
|
|
2021-06-13 13:13:47 +03:00
|
|
|
func showInitialScreen(onAppStart: Bool, wcSession: WCSession?) {
|
2021-06-13 05:45:54 +03:00
|
|
|
let windowController: NSWindowController
|
|
|
|
if onAppStart, let currentWindowController = Window.current {
|
|
|
|
windowController = currentWindowController
|
|
|
|
Window.activate(windowController)
|
|
|
|
} else {
|
|
|
|
windowController = Window.showNew()
|
|
|
|
}
|
|
|
|
|
2021-06-19 00:38:05 +03:00
|
|
|
guard hasPassword else {
|
|
|
|
let welcomeViewController = WelcomeViewController.new { [weak self] createdPassword in
|
|
|
|
guard createdPassword else { return }
|
2021-06-19 19:22:18 +03:00
|
|
|
self?.didEnterPasswordOnStart = true
|
2021-06-19 00:38:05 +03:00
|
|
|
self?.hasPassword = true
|
|
|
|
self?.showInitialScreen(onAppStart: onAppStart, wcSession: wcSession)
|
|
|
|
}
|
|
|
|
windowController.contentViewController = welcomeViewController
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2021-06-19 19:22:18 +03:00
|
|
|
guard didEnterPasswordOnStart else {
|
|
|
|
askAuthentication(on: windowController.window, requireAppPasswordScreen: true, reason: "Start") { [weak self] success in
|
|
|
|
if success {
|
|
|
|
self?.didEnterPasswordOnStart = true
|
|
|
|
self?.showInitialScreen(onAppStart: onAppStart, wcSession: wcSession)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2021-06-17 23:11:03 +03:00
|
|
|
let completion = onSelectedAccount(session: wcSession)
|
2021-06-13 05:45:54 +03:00
|
|
|
let accounts = AccountsService.getAccounts()
|
|
|
|
if !accounts.isEmpty {
|
2021-06-13 06:30:20 +03:00
|
|
|
let accountsList = AccountsListViewController.with(preloadedAccounts: accounts)
|
2021-06-17 23:11:03 +03:00
|
|
|
accountsList.onSelectedAccount = completion
|
2021-06-13 06:30:20 +03:00
|
|
|
windowController.contentViewController = accountsList
|
2021-06-13 05:45:54 +03:00
|
|
|
} else {
|
2021-06-13 06:30:20 +03:00
|
|
|
let importViewController = instantiate(ImportViewController.self)
|
2021-06-17 23:11:03 +03:00
|
|
|
importViewController.onSelectedAccount = completion
|
2021-06-13 06:30:20 +03:00
|
|
|
windowController.contentViewController = importViewController
|
2021-06-13 05:45:54 +03:00
|
|
|
}
|
2021-06-12 19:16:23 +03:00
|
|
|
}
|
|
|
|
|
2021-06-13 09:32:07 +03:00
|
|
|
func showApprove(title: String, meta: String, completion: @escaping (Bool) -> Void) {
|
|
|
|
let windowController = Window.showNew()
|
2021-06-13 15:30:19 +03:00
|
|
|
let approveViewController = ApproveViewController.with(title: title, meta: meta) { [weak self] result in
|
|
|
|
if result {
|
2021-06-19 19:22:18 +03:00
|
|
|
self?.askAuthentication(on: windowController.window, requireAppPasswordScreen: false, reason: title) { success in
|
|
|
|
completion(success)
|
|
|
|
Window.closeAllAndActivateBrowser()
|
|
|
|
}
|
2021-06-13 15:30:19 +03:00
|
|
|
} else {
|
2021-06-19 19:22:18 +03:00
|
|
|
Window.closeAllAndActivateBrowser()
|
2021-06-13 15:30:19 +03:00
|
|
|
completion(result)
|
|
|
|
}
|
2021-06-13 09:32:07 +03:00
|
|
|
}
|
|
|
|
windowController.contentViewController = approveViewController
|
|
|
|
}
|
|
|
|
|
2021-06-13 07:12:39 +03:00
|
|
|
func showErrorMessage(_ message: String) {
|
|
|
|
let windowController = Window.showNew()
|
|
|
|
windowController.contentViewController = ErrorViewController.withMessage(message)
|
|
|
|
}
|
|
|
|
|
2021-06-15 00:42:21 +03:00
|
|
|
func processInputLink(_ link: String) {
|
2021-06-15 23:52:33 +03:00
|
|
|
let session = sessionWithLink(link)
|
2021-06-15 00:42:21 +03:00
|
|
|
showInitialScreen(onAppStart: false, wcSession: session)
|
|
|
|
}
|
|
|
|
|
2021-06-17 23:56:42 +03:00
|
|
|
func getAccountSelectionCompletionIfShouldSelect() -> ((Account) -> Void)? {
|
|
|
|
let session = getSessionFromPasteboard()
|
|
|
|
return onSelectedAccount(session: session)
|
|
|
|
}
|
|
|
|
|
2021-06-19 14:07:57 +03:00
|
|
|
lazy private var statusBarMenu: NSMenu = {
|
|
|
|
let menu = NSMenu(title: "Encrypted Ink")
|
|
|
|
let quitItem = NSMenuItem(title: "Quit", action: #selector(didSelectQuitMenuItem), keyEquivalent: "q")
|
|
|
|
quitItem.target = self
|
|
|
|
menu.delegate = self
|
|
|
|
menu.addItem(quitItem)
|
|
|
|
return menu
|
|
|
|
}()
|
|
|
|
|
2021-06-23 22:21:23 +03:00
|
|
|
func warnBeforeQuitting(updateStatusBarAfterwards: Bool = false) {
|
2021-06-19 15:08:41 +03:00
|
|
|
Window.activateWindow(nil)
|
2021-06-23 22:15:56 +03:00
|
|
|
let alert = Alert()
|
2021-06-19 15:08:41 +03:00
|
|
|
alert.messageText = "Quit Encrypted Ink?"
|
2021-06-19 19:39:43 +03:00
|
|
|
alert.informativeText = "You won't be able to sign requests."
|
2021-06-19 15:08:41 +03:00
|
|
|
alert.alertStyle = .warning
|
|
|
|
alert.addButton(withTitle: "OK")
|
|
|
|
alert.addButton(withTitle: "Cancel")
|
|
|
|
if alert.runModal() == .alertFirstButtonReturn {
|
2021-06-22 20:10:07 +03:00
|
|
|
NSApp.terminate(nil)
|
2021-06-19 15:08:41 +03:00
|
|
|
}
|
2021-06-23 22:21:23 +03:00
|
|
|
if updateStatusBarAfterwards {
|
|
|
|
setupStatusBarItem()
|
|
|
|
}
|
2021-06-19 14:07:57 +03:00
|
|
|
}
|
|
|
|
|
2021-06-23 21:28:52 +03:00
|
|
|
@objc private func didSelectQuitMenuItem() {
|
|
|
|
warnBeforeQuitting()
|
|
|
|
}
|
|
|
|
|
2021-06-19 14:07:57 +03:00
|
|
|
func setupStatusBarItem() {
|
|
|
|
let statusBar = NSStatusBar.system
|
|
|
|
statusBarItem = statusBar.statusItem(withLength: NSStatusItem.squareLength)
|
|
|
|
statusBarItem.button?.image = statusImage
|
|
|
|
statusBarItem.button?.target = self
|
|
|
|
statusBarItem.button?.action = #selector(statusBarButtonClicked(sender:))
|
|
|
|
statusBarItem.button?.sendAction(on: [.leftMouseUp, .rightMouseUp])
|
|
|
|
}
|
|
|
|
|
|
|
|
@objc private func statusBarButtonClicked(sender: NSStatusBarButton) {
|
2021-06-23 22:15:56 +03:00
|
|
|
guard !statusBarButtonIsBlocked else { return }
|
2021-06-19 14:07:57 +03:00
|
|
|
if let event = NSApp.currentEvent, event.type == .rightMouseUp {
|
|
|
|
statusBarItem.menu = statusBarMenu
|
|
|
|
statusBarItem.button?.performClick(nil)
|
|
|
|
} else {
|
|
|
|
checkPasteboardAndOpen(onAppStart: false)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-06-17 23:11:03 +03:00
|
|
|
private func onSelectedAccount(session: WCSession?) -> ((Account) -> Void)? {
|
|
|
|
guard let session = session else { return nil }
|
|
|
|
return { [weak self] account in
|
|
|
|
self?.connectWallet(session: session, account: account)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private func getSessionFromPasteboard() -> WCSession? {
|
2021-06-13 13:13:47 +03:00
|
|
|
let pasteboard = NSPasteboard.general
|
|
|
|
let link = pasteboard.string(forType: .string) ?? ""
|
2021-06-15 23:52:33 +03:00
|
|
|
let session = sessionWithLink(link)
|
|
|
|
if session != nil {
|
|
|
|
pasteboard.clearContents()
|
|
|
|
}
|
2021-06-17 23:11:03 +03:00
|
|
|
return session
|
|
|
|
}
|
|
|
|
|
2021-06-18 00:01:51 +03:00
|
|
|
private func checkPasteboardAndOpen(onAppStart: Bool) {
|
2021-06-17 23:11:03 +03:00
|
|
|
let session = getSessionFromPasteboard()
|
2021-06-18 00:01:51 +03:00
|
|
|
showInitialScreen(onAppStart: onAppStart, wcSession: session)
|
2021-06-15 23:52:33 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
private func sessionWithLink(_ link: String) -> WCSession? {
|
|
|
|
return WalletConnect.shared.sessionWithLink(link)
|
|
|
|
}
|
|
|
|
|
2021-06-19 19:22:18 +03:00
|
|
|
func askAuthentication(on: NSWindow?, getBackTo: NSViewController? = nil, requireAppPasswordScreen: Bool, reason: String, completion: @escaping (Bool) -> Void) {
|
2021-06-13 15:30:19 +03:00
|
|
|
let context = LAContext()
|
2021-06-13 15:39:31 +03:00
|
|
|
var error: NSError?
|
2021-06-19 19:22:18 +03:00
|
|
|
let policy = LAPolicy.deviceOwnerAuthenticationWithBiometrics
|
|
|
|
|
|
|
|
let canDoLocalAuthentication = context.canEvaluatePolicy(policy, error: &error)
|
|
|
|
let willShowPasswordScreen = !canDoLocalAuthentication || requireAppPasswordScreen
|
2021-06-13 15:39:31 +03:00
|
|
|
|
2021-06-20 11:00:58 +03:00
|
|
|
var passwordViewController: PasswordViewController?
|
2021-06-19 19:22:18 +03:00
|
|
|
if willShowPasswordScreen {
|
2021-06-20 11:00:58 +03:00
|
|
|
passwordViewController = PasswordViewController.with(mode: .enter,
|
|
|
|
reason: reason,
|
|
|
|
isDisabledOnStart: canDoLocalAuthentication) { [weak on, weak context] success in
|
2021-06-19 19:22:18 +03:00
|
|
|
if let getBackTo = getBackTo {
|
|
|
|
on?.contentViewController = getBackTo
|
|
|
|
} else {
|
|
|
|
Window.closeAll()
|
|
|
|
}
|
2021-06-19 20:15:46 +03:00
|
|
|
if success {
|
|
|
|
context?.invalidate()
|
|
|
|
}
|
2021-06-13 15:30:19 +03:00
|
|
|
completion(success)
|
|
|
|
}
|
2021-06-19 19:22:18 +03:00
|
|
|
on?.contentViewController = passwordViewController
|
|
|
|
}
|
|
|
|
|
|
|
|
if canDoLocalAuthentication {
|
|
|
|
context.localizedCancelTitle = "Cancel"
|
2021-06-20 11:00:58 +03:00
|
|
|
context.evaluatePolicy(.deviceOwnerAuthentication, localizedReason: reason ) { [weak on, weak passwordViewController] success, _ in
|
2021-06-19 19:22:18 +03:00
|
|
|
DispatchQueue.main.async {
|
2021-06-20 11:00:58 +03:00
|
|
|
passwordViewController?.enableInput()
|
2021-06-19 20:20:32 +03:00
|
|
|
if !success && willShowPasswordScreen && on?.isVisible == true {
|
2021-06-19 20:15:46 +03:00
|
|
|
Window.activateWindow(on)
|
|
|
|
}
|
2021-06-19 19:22:18 +03:00
|
|
|
completion(success)
|
|
|
|
}
|
|
|
|
}
|
2021-06-13 15:30:19 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-06-13 13:13:47 +03:00
|
|
|
private func connectWallet(session: WCSession, account: Account) {
|
|
|
|
WalletConnect.shared.connect(session: session, address: account.address) { [weak self] connected in
|
2021-06-13 07:12:39 +03:00
|
|
|
if connected {
|
2021-06-19 19:38:51 +03:00
|
|
|
Window.closeAllAndActivateBrowser()
|
2021-06-13 07:12:39 +03:00
|
|
|
} else {
|
|
|
|
self?.showErrorMessage("Failed to connect")
|
|
|
|
}
|
2021-06-13 06:30:20 +03:00
|
|
|
}
|
2021-06-13 07:01:01 +03:00
|
|
|
|
|
|
|
let windowController = Window.showNew()
|
|
|
|
windowController.contentViewController = WaitingViewController.withReason("Connecting")
|
2021-06-13 06:30:20 +03:00
|
|
|
}
|
|
|
|
|
2021-06-12 19:16:23 +03:00
|
|
|
}
|
2021-06-19 14:07:57 +03:00
|
|
|
|
|
|
|
extension Agent: NSMenuDelegate {
|
|
|
|
|
|
|
|
func menuDidClose(_ menu: NSMenu) {
|
|
|
|
statusBarItem.menu = nil
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|