Skip to content

Commit

Permalink
Save and restore access token after user authorized within the app (#3)
Browse files Browse the repository at this point in the history
* Save and restore access token after user authorized within the app

* fix tests
  • Loading branch information
ailinykh authored Aug 15, 2023
1 parent 9e97f72 commit dcf97d4
Show file tree
Hide file tree
Showing 23 changed files with 240 additions and 90 deletions.
8 changes: 8 additions & 0 deletions ImgurBar.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,8 @@
738242CE275DE48E00B8757B /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 738242CD275DE48E00B8757B /* AppDelegate.swift */; };
738242DD275DE4BF00B8757B /* ImgurBarHelper.app in Copy Helper */ = {isa = PBXBuildFile; fileRef = 738242CB275DE48E00B8757B /* ImgurBarHelper.app */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
738242F1275DE57900B8757B /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 738242F0275DE57900B8757B /* MainMenu.xib */; };
73B5C5A22A8A4B750068EC59 /* Anuthorization.swift in Sources */ = {isa = PBXBuildFile; fileRef = 73B5C5A12A8A4B750068EC59 /* Anuthorization.swift */; };
73B5C5A42A8BA5EC0068EC59 /* XCTestCase+MemoryLeakTracking.swift in Sources */ = {isa = PBXBuildFile; fileRef = 73B5C5A32A8BA5EC0068EC59 /* XCTestCase+MemoryLeakTracking.swift */; };
73C79B1327774AC700B242CC /* AuthProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 73C79B1227774AC700B242CC /* AuthProvider.swift */; };
73C79B1927774F5200B242CC /* ImgurAuthProviderUseCaseTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 73C79B1827774F5200B242CC /* ImgurAuthProviderUseCaseTests.swift */; };
73D50AF52749931D00E87626 /* UserNotificationProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 73D50AF42749931D00E87626 /* UserNotificationProvider.swift */; };
Expand Down Expand Up @@ -183,6 +185,8 @@
738242D4275DE48F00B8757B /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
738242D5275DE48F00B8757B /* ImgurBarHelper.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = ImgurBarHelper.entitlements; sourceTree = "<group>"; };
738242F0275DE57900B8757B /* MainMenu.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = MainMenu.xib; sourceTree = "<group>"; };
73B5C5A12A8A4B750068EC59 /* Anuthorization.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Anuthorization.swift; sourceTree = "<group>"; };
73B5C5A32A8BA5EC0068EC59 /* XCTestCase+MemoryLeakTracking.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "XCTestCase+MemoryLeakTracking.swift"; sourceTree = "<group>"; };
73C79B1227774AC700B242CC /* AuthProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthProvider.swift; sourceTree = "<group>"; };
73C79B1827774F5200B242CC /* ImgurAuthProviderUseCaseTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImgurAuthProviderUseCaseTests.swift; sourceTree = "<group>"; };
73D50AF42749931D00E87626 /* UserNotificationProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserNotificationProvider.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -247,6 +251,7 @@
7302695327459380005FEB37 /* DropViewTests.swift */,
738241CC275D309000B8757B /* GeneralPrefsViewControllerTests.swift */,
7324697C27B5B75B002C63A8 /* PreferencesPresenterTests.swift */,
73B5C5A32A8BA5EC0068EC59 /* XCTestCase+MemoryLeakTracking.swift */,
);
path = ImgurBarTests;
sourceTree = "<group>";
Expand Down Expand Up @@ -274,6 +279,7 @@
730A2A02272EA37800515A69 /* HTTPClient.swift */,
730A2A00272EA05700515A69 /* Image.swift */,
730A29FE272E9DFF00515A69 /* ImageUploader.swift */,
73B5C5A12A8A4B750068EC59 /* Anuthorization.swift */,
73F5D06B2742FA860005CE1D /* LocalImageProvider.swift */,
73D50AF9274B6B5A00E87626 /* NotificationProvider.swift */,
73F5D05127413B030005CE1D /* RequestBuilder.swift */,
Expand Down Expand Up @@ -606,6 +612,7 @@
buildActionMask = 2147483647;
files = (
7324697D27B5B75B002C63A8 /* PreferencesPresenterTests.swift in Sources */,
73B5C5A42A8BA5EC0068EC59 /* XCTestCase+MemoryLeakTracking.swift in Sources */,
738241CD275D309000B8757B /* GeneralPrefsViewControllerTests.swift in Sources */,
7302695427459380005FEB37 /* DropViewTests.swift in Sources */,
);
Expand All @@ -629,6 +636,7 @@
733A84D42734676900789747 /* HTTPClient.swift in Sources */,
7362788127AFE2170083CF86 /* ImgurAlbumLoader.swift in Sources */,
7362788D27B264C50083CF86 /* KeychainService.swift in Sources */,
73B5C5A22A8A4B750068EC59 /* Anuthorization.swift in Sources */,
7362789027B30EB10083CF86 /* DefaultsService.swift in Sources */,
730A4C962779F83C00CBF944 /* ImgurAuthProvider.swift in Sources */,
7362787127ADB7960083CF86 /* Album.swift in Sources */,
Expand Down
59 changes: 52 additions & 7 deletions ImgurBar/AppDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@
import Cocoa
import ImgurCore

let helperBundleIdentifier = "com.ailinykh.ImgurBarHelper"
extension String {
static let helperBundleIdentifier = "com.ailinykh.ImgurBarHelper"
}

@NSApplicationMain
class AppDelegate: NSObject, NSApplicationDelegate {
Expand Down Expand Up @@ -55,31 +57,56 @@ class AppDelegate: NSObject, NSApplicationDelegate {
return clientId
}()

private lazy var preferencesPresenter: PreferencesPresenter = {
private lazy var localAuthProvider: PersistentAuthProvider = {
#if DEBUG
let storage = DefaultsService()
#else
let storage = KeychainService()
#endif
let localAuthProvider = LocalAuthProvider(storage: storage)
return LocalAuthProvider(storage: storage)
}()

private lazy var preferencesPresenter: PreferencesPresenter = {
let imgurAuthProvider = ImgurAuthProvider(clientId: clientId, client: authClient)
let authProviderMainThreadDecorator = AuthProviderMainThreadDecorator(decoratee: imgurAuthProvider)
return PreferencesPresenter(localAuthProvider: localAuthProvider, remoteAuthProvider: authProviderMainThreadDecorator, screenshotService: screenshotService)
return PreferencesPresenter(
localAuthProvider: localAuthProvider,
remoteAuthProvider: authProviderMainThreadDecorator,
screenshotService: screenshotService)
}()

func applicationDidFinishLaunching(_ aNotification: Notification) {
let _ = statusBarItem // trigger icon appear
terminateLauncherIfNeeded()

let uploader = ImageUploaderMainThreadDecorator(decoratee: ImgurUploader(client: httpClient, clientId: clientId, builder: MultipartFormBuilder()))
var uploader = makeUploader()

NotificationCenter.default.addObserver(
forName: .userAuthorizationStatusChanged,
object: nil,
queue: nil
) { [weak self] note in
if let u = self?.makeUploader(account: note.object as? Account) {
uploader = u
}
}

localAuthProvider.authorize(completion: { [weak self] result in
if let account = try? result.get(), let u = self?.makeUploader(account: account) {
uploader = u
}
})

let facade = LocalImageProviderFacade() { [weak self] localImage in
self?.statusBarItem.button?.startAnimation()

uploader.upload(localImage) { result in
switch (result) {
case .success(let remoteImage):
self?.notificationProvider.sendNotification(identifier: "IMAGE_UPLOADED", title: "Image uploaded", text: remoteImage.url.absoluteString)
self?.notificationProvider.sendNotification(
identifier: .imageUploadCompleted,
title: "Image uploaded",
text: remoteImage.url.absoluteString)
case .failure(let error):
print(error)
}
Expand Down Expand Up @@ -122,10 +149,28 @@ class AppDelegate: NSObject, NSApplicationDelegate {

private func terminateLauncherIfNeeded() {
let runningApps = NSWorkspace.shared.runningApplications
let isRunning = !runningApps.filter { $0.bundleIdentifier == helperBundleIdentifier }.isEmpty
let isRunning = !runningApps.filter {
$0.bundleIdentifier == .helperBundleIdentifier
}.isEmpty

if isRunning {
DistributedNotificationCenter.default().post(name: .terminateLauncher, object: Bundle.main.bundleIdentifier!)
}
}

private func makeUploader(account: Account? = nil) -> ImageUploader {
let auth: Authorization
if let account = account {
auth = .bearerToken(account.accessToken)
} else {
auth = .clientId(clientId)
}
return ImageUploaderMainThreadDecorator(
decoratee: ImgurUploader(
client: httpClient,
auth: auth,
builder: MultipartFormBuilder()
)
)
}
}
7 changes: 4 additions & 3 deletions ImgurBar/Extensions/Notification+ImgurBar.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@
import Foundation

extension Notification.Name {
static let terminateLauncher = Notification.Name("terminateLauncher")
static let applicationOpenUrl = Notification.Name("applicationOpenUrl")
static let authorizationStatusChanged = Notification.Name("authorizationStatusChanged")
static let terminateLauncher = Notification.Name("com.ailinykh.imgurbar.terminateLauncher")
static let applicationOpenUrl = Notification.Name("com.ailinykh.imgurbar.applicationOpenUrl")
static let notificationCenterAuthorizationStatusChanged = Notification.Name("com.ailinykh.imgurbar.notificationCenterAuthorizationStatusChanged")
static let userAuthorizationStatusChanged = Notification.Name("com.ailinykh.imgurbar.userAuthorizationStatusChanged")
}
4 changes: 2 additions & 2 deletions ImgurBar/LaunchOnSystemStartupService.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,12 @@ final class LaunchOnSystemStartupService {
guard let jobs = (LaunchOnSystemStartupService.self as DeprecationWarningWorkaround.Type).jobsDict else {
return false
}
let job = jobs.first { ($0["Label"] as? String) == helperBundleIdentifier }
let job = jobs.first { ($0["Label"] as? String) == .helperBundleIdentifier }
return job?["OnDemand"] as? Bool ?? false
}

func set(value: Bool) {
let bundleId = helperBundleIdentifier as CFString
let bundleId = String.helperBundleIdentifier as CFString
SMLoginItemSetEnabled(bundleId, value)
print("LaunchOnSystemStartupSetting:", value)
}
Expand Down
12 changes: 3 additions & 9 deletions ImgurBar/LocalAuthProvider.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,6 @@ private extension String {
static let account = "account"
}

private struct AccountData: Codable {
let token: String
let username: String
}

final class LocalAuthProvider: PersistentAuthProvider {
private let storage: StorageService

Expand All @@ -22,18 +17,17 @@ final class LocalAuthProvider: PersistentAuthProvider {
}

func authorize(completion: @escaping (Result<Account, Error>) -> Void) {
guard let data = storage.get(for: .account), let account = try? JSONDecoder().decode(AccountData.self, from: data) else {
guard let data = storage.get(for: .account), let account = try? JSONDecoder().decode(Account.self, from: data) else {
let error = NSError(domain: "not found", code: -1)
completion(.failure(error))
return
}

completion(.success(Account(token: account.token, username: account.username)))
completion(.success(account))
}

func save(account: Account) {
let accountData = AccountData(token: account.token, username: account.username)
let data = try! JSONEncoder().encode(accountData)
let data = try! JSONEncoder().encode(account)
storage.set(data: data, for: .account)
}

Expand Down
6 changes: 5 additions & 1 deletion ImgurBar/NotificationAuthorizationFacade.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,11 @@ class NotificationAuthorizationFacade: NSObject {

override init() {
super.init()
NotificationCenter.default.addObserver(forName: .authorizationStatusChanged, object: nil, queue: nil) { [weak self] note in
NotificationCenter.default.addObserver(
forName: .notificationCenterAuthorizationStatusChanged,
object: nil,
queue: nil
) { [weak self] note in
guard let authorized = note.object as? Bool else {
print("expected `note.object` as boolean", note)
return
Expand Down
9 changes: 5 additions & 4 deletions ImgurBar/Preferences/GeneralPrefsViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ class AccountViewModel: NSObject {

var onLogin = {}
var onLogout = {}
var onViewDidAppear = {}
}

class GeneralPrefsViewController: NSViewController {
Expand All @@ -31,10 +32,10 @@ class GeneralPrefsViewController: NSViewController {
}

@objc dynamic var accountViewModel: AccountViewModel?

override func viewDidLoad() {
super.viewDidLoad()
// Do view setup here.
override func viewDidAppear() {
super.viewDidAppear()
accountViewModel?.onViewDidAppear()
}

func display(_ model: AccountViewModel) {
Expand Down
82 changes: 46 additions & 36 deletions ImgurBar/Preferences/PreferencesPresenter.swift
Original file line number Diff line number Diff line change
Expand Up @@ -20,51 +20,61 @@ final class PreferencesPresenter {
func makeController() -> PreferencesWindowController {
let storyboard = NSStoryboard(name: "Preferences", bundle: .main)
let windowController = storyboard.instantiateInitialController() as! PreferencesWindowController
let tab = windowController.window?.contentViewController as! PreferencesTabViewController
let vc = tab.tabViewItems.first?.viewController as! GeneralPrefsViewController

if let tab = windowController.window?.contentViewController as? PreferencesTabViewController,
let vc = tab.tabViewItems.first?.viewController as? GeneralPrefsViewController {

let startupService = LaunchOnSystemStartupService()
vc.launchOnSystemStartup = startupService.get()
vc.onLaunchOnSystemStartupChanged = startupService.set

vc.uploadScreenshots = screenshotService.get()
vc.onUploadScreenshotsChanged = screenshotService.set

localAuthProvider.authorize { [weak self] result in
switch result {
case .success(let account):
self?.handle(account: account, viewController: vc)
case .failure:
let accountViewModel = AccountViewModel()
accountViewModel.onLogin = {
self?.remoteAuthProvider.authorize { result in
switch result {
case .success(let account):
print("Got token:", account.token)
self?.localAuthProvider.save(account: account)
self?.handle(account: account, viewController: vc)
case .failure(let error):
print("Auth failed", error)
}
}
}
vc.display(accountViewModel)
}
}
}
let startupService = LaunchOnSystemStartupService()
vc.launchOnSystemStartup = startupService.get()
vc.onLaunchOnSystemStartupChanged = startupService.set

vc.uploadScreenshots = screenshotService.get()
vc.onUploadScreenshotsChanged = screenshotService.set

vc.display(makeModel(viewController: vc))
return windowController
}

private func handle(account: Account, viewController: GeneralPrefsViewController) {
private func handle(account: Account, viewController: GeneralPrefsViewController?) {
let model = makeModel(
viewController: viewController,
name: account.username,
authorized: true)
viewController?.display(model)
}

private func makeModel(viewController: GeneralPrefsViewController?, name: String = "", authorized: Bool = false) -> AccountViewModel {
let accountViewModel = AccountViewModel()
accountViewModel.onLogout = { [weak self] in
print("logout")
self?.localAuthProvider.remove()
NotificationCenter.default.post(
name: .userAuthorizationStatusChanged,
object: nil)
}
accountViewModel.onLogin = { [weak self, weak viewController] in
self?.remoteAuthProvider.authorize { result in
switch result {
case .success(let account):
print("Got account:", account)
self?.localAuthProvider.save(account: account)
self?.handle(account: account, viewController: viewController)
NotificationCenter.default.post(
name: .userAuthorizationStatusChanged,
object: account)
case .failure(let error):
print("Auth failed", error)
}
}
}
accountViewModel.onViewDidAppear = { [weak self, weak viewController] in
self?.localAuthProvider.authorize { result in
if let account = try? result.get() {
self?.handle(account: account, viewController: viewController)
}
}
}
accountViewModel.name = account.username
accountViewModel.authorized = true
viewController.display(accountViewModel)
accountViewModel.name = name
accountViewModel.authorized = authorized
return accountViewModel
}
}
4 changes: 4 additions & 0 deletions ImgurBar/Preferences/PreferencesWindowController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@ import Cocoa

class PreferencesWindowController: NSWindowController {

deinit {
print(#function, self)
}

override func windowDidLoad() {
super.windowDidLoad()
}
Expand Down
Loading

0 comments on commit dcf97d4

Please sign in to comment.