From 5ad92bf2fdb82b7bc8a5f961bf839293f53b81d3 Mon Sep 17 00:00:00 2001 From: Serhiy Mytrovtsiy Date: Thu, 3 Apr 2025 18:53:31 +0200 Subject: [PATCH] feat: updated login process to Stats Remote --- Kit/helpers.swift | 7 +++ Kit/plugins/Remote.swift | 75 ++++++++++++++++++++--------- Stats/Supporting Files/Info.plist | 2 +- Stats/Views/AppSettings.swift | 59 ++++++++++++----------- Widgets/Supporting Files/Info.plist | 2 +- 5 files changed, 92 insertions(+), 53 deletions(-) diff --git a/Kit/helpers.swift b/Kit/helpers.swift index 4b19d381..b5c0fd4c 100644 --- a/Kit/helpers.swift +++ b/Kit/helpers.swift @@ -1316,6 +1316,13 @@ public class PreferencesSection: NSStackView { } public func setRowVisibility(_ at: Int, newState: Bool) { + if at == 0 { + self.container.subviews[0].isHidden = !newState + if self.container.subviews.count > 1 { + self.container.subviews[1].isHidden = !newState + } + return + } for i in self.container.subviews.indices where i/2 == at && Double(i).remainder(dividingBy: 2) == 0 { self.container.subviews[i-1].isHidden = !newState self.container.subviews[i].isHidden = !newState diff --git a/Kit/plugins/Remote.swift b/Kit/plugins/Remote.swift index bfaeafc3..60c64ce5 100644 --- a/Kit/plugins/Remote.swift +++ b/Kit/plugins/Remote.swift @@ -14,15 +14,26 @@ import Cocoa public class Remote { public static let shared = Remote() - static public var host = URL(string: "https://api.system-stats.com")! // https://api.system-stats.com http://localhost:8008 + static public var host = URL(string: "http://localhost:8008")! // https://api.system-stats.com http://localhost:8008 - public var state: Bool { - get { Store.shared.bool(key: "remote_state", defaultValue: false) } + public var monitoring: Bool { + get { Store.shared.bool(key: "remote_monitoring", defaultValue: false) } set { - Store.shared.set(key: "remote_state", value: newValue) + Store.shared.set(key: "remote_monitoring", value: newValue) if newValue { self.start() - } else { + } else if !self.control { + self.stop() + } + } + } + public var control: Bool { + get { Store.shared.bool(key: "remote_control", defaultValue: false) } + set { + Store.shared.set(key: "remote_control", value: newValue) + if newValue { + self.start() + } else if !self.monitoring { self.stop() } } @@ -31,19 +42,18 @@ public class Remote { public var isAuthorized: Bool = false public var auth: RemoteAuth = RemoteAuth() + private let log: NextLog private var ws: WebSocketManager = WebSocketManager() private var wsURL: URL? private var isConnecting = false public init() { + self.log = NextLog.shared.copy(category: "Remote") self.id = UUID(uuidString: Store.shared.string(key: "telemetry_id", defaultValue: UUID().uuidString)) ?? UUID() - DispatchQueue.main.asyncAfter(deadline: .now() + 1) { - if self.state { - self.start() - } else { - self.stop() - } + if self.auth.hasCredentials() { + info("Found auth credentials for remote monitoring, starting Remote...", log: self.log) + self.start() } NotificationCenter.default.addObserver(self, selector: #selector(self.successLogin), name: .remoteLoginSuccess, object: nil) @@ -56,7 +66,11 @@ public class Remote { public func login() { self.auth.login { url in - guard let url else { return } + guard let url else { + error("Empty url when try to login", log: self.log) + return + } + debug("Open \(url) to login to Stats Remote", log: self.log) NSWorkspace.shared.open(url) } } @@ -64,24 +78,21 @@ public class Remote { public func logout() { self.auth.logout() self.isAuthorized = false - self.state = false self.ws.disconnect() - NotificationCenter.default.post(name: .remoteState, object: nil, userInfo: ["auth": self.isAuthorized, "state": self.state]) + debug("Logout successfully from Stats Remote", log: self.log) + NotificationCenter.default.post(name: .remoteState, object: nil, userInfo: ["auth": self.isAuthorized]) } public func send(key: String, value: Codable) { - guard self.state && self.isAuthorized, - let blobData = try? JSONEncoder().encode(value) else { return } + guard self.monitoring && self.isAuthorized, let blobData = try? JSONEncoder().encode(value) else { return } self.ws.send(key: key, data: blobData) } @objc private func successLogin() { self.isAuthorized = true - NotificationCenter.default.post(name: .remoteState, object: nil, userInfo: ["auth": self.isAuthorized, "state": self.state]) - - if self.state { - self.ws.connect() - } + NotificationCenter.default.post(name: .remoteState, object: nil, userInfo: ["auth": self.isAuthorized]) + self.ws.connect() + debug("Login successfully on Stats Remote", log: self.log) } public func start() { @@ -89,7 +100,7 @@ public class Remote { guard let self else { return } self.isAuthorized = status - NotificationCenter.default.post(name: .remoteState, object: nil, userInfo: ["auth": self.isAuthorized, "state": self.state]) + NotificationCenter.default.post(name: .remoteState, object: nil, userInfo: ["auth": self.isAuthorized]) if status { self.ws.connect() @@ -99,7 +110,7 @@ public class Remote { private func stop() { self.ws.disconnect() - NotificationCenter.default.post(name: .remoteState, object: nil, userInfo: ["auth": self.isAuthorized, "state": self.state]) + NotificationCenter.default.post(name: .remoteState, object: nil, userInfo: ["auth": self.isAuthorized]) } } @@ -128,8 +139,15 @@ public class RemoteAuth { } public func isAuthorized(completion: @escaping (Bool) -> Void) { + if !self.hasCredentials() { + completion(false) + return + } self.validate(completion) } + public func hasCredentials() -> Bool { + return !self.accessToken.isEmpty && !self.refreshToken.isEmpty + } public func login(completion: @escaping (URL?) -> Void) { self.registerDevice { device in @@ -335,14 +353,17 @@ class WebSocketManager: NSObject { private let reconnectDelay: TimeInterval = 3.0 private var pingTimer: Timer? private var reachability: Reachability = Reachability(start: true) + private let log: NextLog override init() { + self.log = NextLog.shared.copy(category: "Remote WS") + super.init() self.session = URLSession(configuration: .default, delegate: self, delegateQueue: .main) self.reachability.reachable = { - if Remote.shared.state { + if Remote.shared.isAuthorized { self.connect() } } @@ -367,19 +388,25 @@ class WebSocketManager: NSObject { self.webSocket?.resume() self.receiveMessage() self.isDisconnected = false + debug("connected successfully", log: self.log) } } public func disconnect() { + if self.webSocket == nil && !self.isConnected { return } self.isDisconnected = true self.webSocket?.cancel(with: .normalClosure, reason: nil) self.webSocket = nil self.isConnected = false + debug("disconnected gracefully", log: self.log) } private func reconnect() { guard !self.isDisconnected else { return } DispatchQueue.main.asyncAfter(deadline: .now() + self.reconnectDelay) { [weak self] in + if let log = self?.log { + debug("trying to reconnect after some interruption", log: log) + } self?.connect() } } diff --git a/Stats/Supporting Files/Info.plist b/Stats/Supporting Files/Info.plist index 3f6e9567..93001f5d 100755 --- a/Stats/Supporting Files/Info.plist +++ b/Stats/Supporting Files/Info.plist @@ -17,7 +17,7 @@ CFBundleShortVersionString $(MARKETING_VERSION) CFBundleVersion - 689 + 690 Description Simple macOS system monitor in your menu bar LSApplicationCategoryType diff --git a/Stats/Views/AppSettings.swift b/Stats/Views/AppSettings.swift index 49b78fa4..445699bc 100644 --- a/Stats/Views/AppSettings.swift +++ b/Stats/Views/AppSettings.swift @@ -42,7 +42,6 @@ class ApplicationSettings: NSStackView { private var updateSelector: NSPopUpButton? private var startAtLoginBtn: NSSwitch? private var telemetryBtn: NSSwitch? - private var remoteBtn: NSSwitch? private var combinedModulesView: PreferencesSection? private var fanHelperView: PreferencesSection? @@ -129,17 +128,19 @@ class ApplicationSettings: NSStackView { self.combinedModulesView?.setRowVisibility(3, newState: self.combinedModulesState) self.combinedModulesView?.setRowVisibility(4, newState: self.combinedModulesState) - self.remoteBtn = switchView( - action: #selector(self.toggleRemoteState), - state: Remote.shared.state - ) - - self.remoteView = PreferencesSection(label: localizedString("Stats Remote"), [ - PreferencesRow(localizedString("Monitoring"), component: self.remoteBtn!), + self.remoteView = PreferencesSection(label: localizedString("Stats Remote (beta)"), [ + PreferencesRow(localizedString("Authorization"), component: buttonView(#selector(self.loginToRemote), text: localizedString("Login"))), PreferencesRow(localizedString("Identificator"), component: textView(Remote.shared.id.uuidString)), + PreferencesRow(localizedString("Monitoring"), component: switchView( + action: #selector(self.toggleRemoteMonitoringState), + state: Remote.shared.monitoring + )), PreferencesRow(component: buttonView(#selector(self.logoutFromRemote), text: localizedString("Logout"))) ]) scrollView.stackView.addArrangedSubview(self.remoteView!) + self.remoteView?.setRowVisibility(1, newState: false) + self.remoteView?.setRowVisibility(2, newState: false) + self.remoteView?.setRowVisibility(3, newState: false) scrollView.stackView.addArrangedSubview(PreferencesSection(label: localizedString("Settings"), [ PreferencesRow( @@ -184,7 +185,7 @@ class ApplicationSettings: NSStackView { scrollView.stackView.addArrangedSubview(PreferencesSection(label: localizedString("Stress tests"), tests)) NotificationCenter.default.addObserver(self, selector: #selector(self.toggleUninstallHelperButton), name: .fanHelperState, object: nil) - NotificationCenter.default.addObserver(self, selector: #selector(self.remoteState), name: .remoteState, object: nil) + NotificationCenter.default.addObserver(self, selector: #selector(self.handleRemoteState), name: .remoteState, object: nil) } required init?(coder: NSCoder) { @@ -420,34 +421,38 @@ class ApplicationSettings: NSStackView { } } + @objc private func toggleRemoteMonitoringState(_ sender: NSButton) { + Remote.shared.monitoring = sender.state == NSControl.StateValue.on + } + + @objc private func handleRemoteState(_ notification: Notification) { + guard let auth = notification.userInfo?["auth"] as? Bool else { return } + self.setRemoteSettings(auth) + } + + @objc private func loginToRemote() { + Remote.shared.login() + } + @objc private func logoutFromRemote() { Remote.shared.logout() } - @objc private func remoteState(_ notification: Notification) { - guard let state = notification.userInfo?["state"] as? Bool, let auth = notification.userInfo?["auth"] as? Bool else { return } - self.setRemoteSettings(state, auth) - } - - private func setRemoteSettings(_ state: Bool, _ auth: Bool) { + private func setRemoteSettings(_ auth: Bool) { DispatchQueue.main.async { - if state && auth { - self.remoteBtn?.state = .on + if auth { self.remoteView?.setRowVisibility(1, newState: true) self.remoteView?.setRowVisibility(2, newState: true) - return - } else if state && !auth { - Remote.shared.login() + self.remoteView?.setRowVisibility(3, newState: true) + self.remoteView?.setRowVisibility(0, newState: false) + } else { + self.remoteView?.setRowVisibility(0, newState: true) + self.remoteView?.setRowVisibility(1, newState: false) + self.remoteView?.setRowVisibility(2, newState: false) + self.remoteView?.setRowVisibility(3, newState: false) } - self.remoteBtn?.state = .off - self.remoteView?.setRowVisibility(1, newState: false) - self.remoteView?.setRowVisibility(2, newState: false) } } - - @objc private func toggleRemoteState(_ sender: NSButton) { - Remote.shared.state = sender.state == NSControl.StateValue.on - } } private class ModuleSelectorView: NSStackView { diff --git a/Widgets/Supporting Files/Info.plist b/Widgets/Supporting Files/Info.plist index 95f9f6f3..5b84a3ce 100644 --- a/Widgets/Supporting Files/Info.plist +++ b/Widgets/Supporting Files/Info.plist @@ -13,7 +13,7 @@ CFBundleShortVersionString 2.11.37 CFBundleVersion - 689 + 690 NSExtension NSExtensionPointIdentifier