fix(macos): live-check Pi oauth.json
parent
caaa79bb76
commit
26a05292b9
|
|
@ -119,35 +119,55 @@ enum PiOAuthStore {
|
||||||
}
|
}
|
||||||
|
|
||||||
static func hasAnthropicOAuth() -> Bool {
|
static func hasAnthropicOAuth() -> Bool {
|
||||||
guard let dict = (try? self.loadStorage()) else { return false }
|
let url = self.oauthURL()
|
||||||
return dict[self.providerKey] != nil
|
guard FileManager.default.fileExists(atPath: url.path) else { return false }
|
||||||
|
|
||||||
|
guard let data = try? Data(contentsOf: url),
|
||||||
|
let json = try? JSONSerialization.jsonObject(with: data, options: []),
|
||||||
|
let storage = json as? [String: Any],
|
||||||
|
let entry = storage[self.providerKey] as? [String: Any]
|
||||||
|
else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
let refresh = entry["refresh"] as? String
|
||||||
|
let access = entry["access"] as? String
|
||||||
|
return (refresh?.isEmpty == false) && (access?.isEmpty == false)
|
||||||
}
|
}
|
||||||
|
|
||||||
static func saveAnthropicOAuth(_ creds: AnthropicOAuthCredentials) throws {
|
static func saveAnthropicOAuth(_ creds: AnthropicOAuthCredentials) throws {
|
||||||
var storage = (try? self.loadStorage()) ?? [:]
|
|
||||||
storage[self.providerKey] = creds
|
|
||||||
try self.saveStorage(storage)
|
|
||||||
}
|
|
||||||
|
|
||||||
private static func loadStorage() throws -> [String: AnthropicOAuthCredentials] {
|
|
||||||
let url = self.oauthURL()
|
let url = self.oauthURL()
|
||||||
guard FileManager.default.fileExists(atPath: url.path) else { return [:] }
|
let existing: [String: Any]
|
||||||
let data = try Data(contentsOf: url)
|
if FileManager.default.fileExists(atPath: url.path),
|
||||||
return try JSONDecoder().decode([String: AnthropicOAuthCredentials].self, from: data)
|
let data = try? Data(contentsOf: url),
|
||||||
|
let json = try? JSONSerialization.jsonObject(with: data, options: []),
|
||||||
|
let dict = json as? [String: Any]
|
||||||
|
{
|
||||||
|
existing = dict
|
||||||
|
} else {
|
||||||
|
existing = [:]
|
||||||
|
}
|
||||||
|
|
||||||
|
var updated = existing
|
||||||
|
updated[self.providerKey] = [
|
||||||
|
"type": creds.type,
|
||||||
|
"refresh": creds.refresh,
|
||||||
|
"access": creds.access,
|
||||||
|
"expires": creds.expires,
|
||||||
|
]
|
||||||
|
|
||||||
|
try self.saveStorage(updated)
|
||||||
}
|
}
|
||||||
|
|
||||||
private static func saveStorage(_ storage: [String: AnthropicOAuthCredentials]) throws {
|
private static func saveStorage(_ storage: [String: Any]) throws {
|
||||||
let dir = self.oauthDir()
|
let dir = self.oauthDir()
|
||||||
try FileManager.default.createDirectory(
|
try FileManager.default.createDirectory(
|
||||||
at: dir,
|
at: dir,
|
||||||
withIntermediateDirectories: true,
|
withIntermediateDirectories: true,
|
||||||
attributes: [.posixPermissions: 0o700])
|
attributes: [.posixPermissions: 0o700])
|
||||||
|
|
||||||
let encoder = JSONEncoder()
|
|
||||||
encoder.outputFormatting = [.prettyPrinted, .sortedKeys]
|
|
||||||
let data = try encoder.encode(storage)
|
|
||||||
|
|
||||||
let url = self.oauthURL()
|
let url = self.oauthURL()
|
||||||
|
let data = try JSONSerialization.data(withJSONObject: storage, options: [.prettyPrinted, .sortedKeys])
|
||||||
try data.write(to: url, options: [.atomic])
|
try data.write(to: url, options: [.atomic])
|
||||||
try FileManager.default.setAttributes([.posixPermissions: 0o600], ofItemAtPath: url.path)
|
try FileManager.default.setAttributes([.posixPermissions: 0o600], ofItemAtPath: url.path)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -57,6 +57,8 @@ struct OnboardingView: View {
|
||||||
@State private var anthropicAuthStatus: String?
|
@State private var anthropicAuthStatus: String?
|
||||||
@State private var anthropicAuthBusy = false
|
@State private var anthropicAuthBusy = false
|
||||||
@State private var anthropicAuthConnected = false
|
@State private var anthropicAuthConnected = false
|
||||||
|
@State private var monitoringAuth = false
|
||||||
|
@State private var authMonitorTask: Task<Void, Never>?
|
||||||
@State private var identityName: String = ""
|
@State private var identityName: String = ""
|
||||||
@State private var identityTheme: String = ""
|
@State private var identityTheme: String = ""
|
||||||
@State private var identityEmoji: String = ""
|
@State private var identityEmoji: String = ""
|
||||||
|
|
@ -73,6 +75,7 @@ struct OnboardingView: View {
|
||||||
private let pageWidth: CGFloat = 680
|
private let pageWidth: CGFloat = 680
|
||||||
private let contentHeight: CGFloat = 520
|
private let contentHeight: CGFloat = 520
|
||||||
private let connectionPageIndex = 1
|
private let connectionPageIndex = 1
|
||||||
|
private let anthropicAuthPageIndex = 2
|
||||||
private let permissionsPageIndex = 5
|
private let permissionsPageIndex = 5
|
||||||
private var pageOrder: [Int] {
|
private var pageOrder: [Int] {
|
||||||
if self.state.connectionMode == .remote {
|
if self.state.connectionMode == .remote {
|
||||||
|
|
@ -149,6 +152,7 @@ struct OnboardingView: View {
|
||||||
.onDisappear {
|
.onDisappear {
|
||||||
self.stopPermissionMonitoring()
|
self.stopPermissionMonitoring()
|
||||||
self.stopDiscovery()
|
self.stopDiscovery()
|
||||||
|
self.stopAuthMonitoring()
|
||||||
}
|
}
|
||||||
.task {
|
.task {
|
||||||
await self.refreshPerms()
|
await self.refreshPerms()
|
||||||
|
|
@ -324,6 +328,26 @@ struct OnboardingView: View {
|
||||||
.foregroundStyle(.secondary)
|
.foregroundStyle(.secondary)
|
||||||
.fixedSize(horizontal: false, vertical: true)
|
.fixedSize(horizontal: false, vertical: true)
|
||||||
|
|
||||||
|
HStack(spacing: 12) {
|
||||||
|
Text(PiOAuthStore.oauthURL().path)
|
||||||
|
.font(.caption)
|
||||||
|
.foregroundStyle(.secondary)
|
||||||
|
.lineLimit(1)
|
||||||
|
.truncationMode(.middle)
|
||||||
|
|
||||||
|
Spacer()
|
||||||
|
|
||||||
|
Button("Reveal") {
|
||||||
|
NSWorkspace.shared.activateFileViewerSelecting([PiOAuthStore.oauthURL()])
|
||||||
|
}
|
||||||
|
.buttonStyle(.bordered)
|
||||||
|
|
||||||
|
Button("Refresh") {
|
||||||
|
self.refreshAnthropicOAuthStatus()
|
||||||
|
}
|
||||||
|
.buttonStyle(.bordered)
|
||||||
|
}
|
||||||
|
|
||||||
Divider().padding(.vertical, 2)
|
Divider().padding(.vertical, 2)
|
||||||
|
|
||||||
HStack(spacing: 12) {
|
HStack(spacing: 12) {
|
||||||
|
|
@ -1070,6 +1094,7 @@ struct OnboardingView: View {
|
||||||
private func updateMonitoring(for pageIndex: Int) {
|
private func updateMonitoring(for pageIndex: Int) {
|
||||||
self.updatePermissionMonitoring(for: pageIndex)
|
self.updatePermissionMonitoring(for: pageIndex)
|
||||||
self.updateDiscoveryMonitoring(for: pageIndex)
|
self.updateDiscoveryMonitoring(for: pageIndex)
|
||||||
|
self.updateAuthMonitoring(for: pageIndex)
|
||||||
}
|
}
|
||||||
|
|
||||||
private func stopPermissionMonitoring() {
|
private func stopPermissionMonitoring() {
|
||||||
|
|
@ -1084,6 +1109,33 @@ struct OnboardingView: View {
|
||||||
self.masterDiscovery.stop()
|
self.masterDiscovery.stop()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private func updateAuthMonitoring(for pageIndex: Int) {
|
||||||
|
let shouldMonitor = pageIndex == self.anthropicAuthPageIndex && self.state.connectionMode == .local
|
||||||
|
if shouldMonitor, !self.monitoringAuth {
|
||||||
|
self.monitoringAuth = true
|
||||||
|
self.startAuthMonitoring()
|
||||||
|
} else if !shouldMonitor, self.monitoringAuth {
|
||||||
|
self.stopAuthMonitoring()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func startAuthMonitoring() {
|
||||||
|
self.refreshAnthropicOAuthStatus()
|
||||||
|
self.authMonitorTask?.cancel()
|
||||||
|
self.authMonitorTask = Task {
|
||||||
|
while !Task.isCancelled {
|
||||||
|
await MainActor.run { self.refreshAnthropicOAuthStatus() }
|
||||||
|
try? await Task.sleep(nanoseconds: 1_000_000_000)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func stopAuthMonitoring() {
|
||||||
|
self.monitoringAuth = false
|
||||||
|
self.authMonitorTask?.cancel()
|
||||||
|
self.authMonitorTask = nil
|
||||||
|
}
|
||||||
|
|
||||||
private func installCLI() async {
|
private func installCLI() async {
|
||||||
guard !self.installingCLI else { return }
|
guard !self.installingCLI else { return }
|
||||||
self.installingCLI = true
|
self.installingCLI = true
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue