Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

refactor: merge session & settings abstractions#46

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to ourterms of service andprivacy statement. We’ll occasionally send you account related emails.

Already on GitHub?Sign in to your account

Merged
ethanndickson merged 1 commit intomainfromethan/state-merge
Feb 19, 2025
Merged
Show file tree
Hide file tree
Changes fromall commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 8 additions & 12 deletionsCoder Desktop/Coder Desktop/Coder_DesktopApp.swift
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -11,15 +11,14 @@ struct DesktopApp: App {
EmptyView()
}
Window("Sign In", id: Windows.login.rawValue) {
LoginForm<SecureSession>()
.environmentObject(appDelegate.session)
.environmentObject(appDelegate.settings)
LoginForm()
.environmentObject(appDelegate.state)
}
.windowResizability(.contentSize)
SwiftUI.Settings {
SettingsView<CoderVPNService>()
.environmentObject(appDelegate.vpn)
.environmentObject(appDelegate.settings)
.environmentObject(appDelegate.state)
}
.windowResizability(.contentSize)
}
Expand All@@ -29,28 +28,25 @@ struct DesktopApp: App {
class AppDelegate: NSObject, NSApplicationDelegate {
private var menuBarExtra: FluidMenuBarExtra?
let vpn: CoderVPNService
let session: SecureSession
let settings: Settings
let state: AppState

override init() {
vpn = CoderVPNService()
settings = Settings()
session = SecureSession(onChange: vpn.configureTunnelProviderProtocol)
state = AppState(onChange: vpn.configureTunnelProviderProtocol)
}

func applicationDidFinishLaunching(_: Notification) {
menuBarExtra = FluidMenuBarExtra(title: "Coder Desktop", image: "MenuBarIcon") {
VPNMenu<CoderVPNService, SecureSession>().frame(width: 256)
VPNMenu<CoderVPNService>().frame(width: 256)
.environmentObject(self.vpn)
.environmentObject(self.session)
.environmentObject(self.settings)
.environmentObject(self.state)
}
}

// This function MUST eventually call `NSApp.reply(toApplicationShouldTerminate: true)`
// or return `.terminateNow`
func applicationShouldTerminate(_: NSApplication) -> NSApplication.TerminateReply {
if !settings.stopVPNOnQuit { return .terminateNow }
if !state.stopVPNOnQuit { return .terminateNow }
Task {
await vpn.stop()
NSApp.reply(toApplicationShouldTerminate: true)
Expand Down
View file
Open in desktop

This file was deleted.

84 changes: 43 additions & 41 deletionsCoder Desktop/Coder Desktop/State.swift
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -4,28 +4,20 @@ import KeychainAccess
import NetworkExtension
import SwiftUI

protocol Session: ObservableObject {
var hasSession: Bool { get }
var baseAccessURL: URL? { get }
var sessionToken: String? { get }

func store(baseAccessURL: URL, sessionToken: String)
func clear()
func tunnelProviderProtocol() -> NETunnelProviderProtocol?
}

class SecureSession: ObservableObject, Session {
class AppState: ObservableObject {
let appId = Bundle.main.bundleIdentifier!

// Stored in UserDefaults
@Published private(set) var hasSession: Bool {
didSet {
guard persistent else { return }
UserDefaults.standard.set(hasSession, forKey: Keys.hasSession)
}
}

@Published private(set) var baseAccessURL: URL? {
didSet {
guard persistent else { return }
UserDefaults.standard.set(baseAccessURL, forKey: Keys.baseAccessURL)
}
}
Expand All@@ -37,6 +29,27 @@ class SecureSession: ObservableObject, Session {
}
}

@Published var useLiteralHeaders: Bool = UserDefaults.standard.bool(forKey: Keys.useLiteralHeaders) {
didSet {
guard persistent else { return }
UserDefaults.standard.set(useLiteralHeaders, forKey: Keys.useLiteralHeaders)
}
}

@Published var literalHeaders: [LiteralHeader] {
didSet {
guard persistent else { return }
try? UserDefaults.standard.set(JSONEncoder().encode(literalHeaders), forKey: Keys.literalHeaders)
}
}

@Published var stopVPNOnQuit: Bool = UserDefaults.standard.bool(forKey: Keys.stopVPNOnQuit) {
didSet {
guard persistent else { return }
UserDefaults.standard.set(stopVPNOnQuit, forKey: Keys.stopVPNOnQuit)
}
}

func tunnelProviderProtocol() -> NETunnelProviderProtocol? {
if !hasSession { return nil }
let proto = NETunnelProviderProtocol()
Expand All@@ -49,37 +62,50 @@ class SecureSession: ObservableObject, Session {
}

private let keychain: Keychain
private let persistent: Bool

let onChange: ((NETunnelProviderProtocol?) -> Void)?

public init(onChange: ((NETunnelProviderProtocol?) -> Void)? = nil) {
public init(onChange: ((NETunnelProviderProtocol?) -> Void)? = nil,
persistent: Bool = true)
{
self.persistent = persistent
self.onChange = onChange
keychain = Keychain(service: Bundle.main.bundleIdentifier!)
_hasSession = Published(initialValue: UserDefaults.standard.bool(forKey: Keys.hasSession))
_baseAccessURL = Published(initialValue: UserDefaults.standard.url(forKey: Keys.baseAccessURL))
_hasSession = Published(initialValue: persistent ? UserDefaults.standard.bool(forKey: Keys.hasSession) : false)
_baseAccessURL = Published(
initialValue: persistent ? UserDefaults.standard.url(forKey: Keys.baseAccessURL) : nil
)
_literalHeaders = Published(
initialValue: persistent ? UserDefaults.standard.data(
forKey: Keys.literalHeaders
).flatMap { try? JSONDecoder().decode([LiteralHeader].self, from: $0) } ?? [] : []
)
if hasSession {
_sessionToken = Published(initialValue: keychainGet(for: Keys.sessionToken))
}
}

public funcstore(baseAccessURL: URL, sessionToken: String) {
public funclogin(baseAccessURL: URL, sessionToken: String) {
hasSession = true
self.baseAccessURL = baseAccessURL
self.sessionToken = sessionToken
if let onChange { onChange(tunnelProviderProtocol()) }
}

public funcclear() {
public funcclearSession() {
hasSession = false
sessionToken = nil
if let onChange { onChange(tunnelProviderProtocol()) }
}

private func keychainGet(for key: String) -> String? {
try? keychain.getString(key)
guard persistent else { return nil }
return try? keychain.getString(key)
}

private func keychainSet(_ value: String?, for key: String) {
guard persistent else { return }
if let value {
try? keychain.set(value, key: key)
} else {
Expand All@@ -91,31 +117,7 @@ class SecureSession: ObservableObject, Session {
static let hasSession = "hasSession"
static let baseAccessURL = "baseAccessURL"
static let sessionToken = "sessionToken"
}
}

class Settings: ObservableObject {
private let store: UserDefaults
@AppStorage(Keys.useLiteralHeaders) var useLiteralHeaders = false

@Published var literalHeaders: [LiteralHeader] {
didSet {
try? store.set(JSONEncoder().encode(literalHeaders), forKey: Keys.literalHeaders)
}
}

@AppStorage(Keys.stopVPNOnQuit) var stopVPNOnQuit = true

init(store: UserDefaults = .standard) {
self.store = store
_literalHeaders = Published(
initialValue: store.data(
forKey: Keys.literalHeaders
).flatMap { try? JSONDecoder().decode([LiteralHeader].self, from: $0) } ?? []
)
}

enum Keys {
static let useLiteralHeaders = "UseLiteralHeaders"
static let literalHeaders = "LiteralHeaders"
static let stopVPNOnQuit = "StopVPNOnQuit"
Expand Down
6 changes: 3 additions & 3 deletionsCoder Desktop/Coder Desktop/Views/Agents.swift
View file
Open in desktop
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
import SwiftUI

struct Agents<VPN: VPNService, S: Session>: View {
struct Agents<VPN: VPNService>: View {
@EnvironmentObject var vpn: VPN
@EnvironmentObject varsession: S
@EnvironmentObject varstate: AppState
@State private var viewAll = false
private let defaultVisibleRows = 5

Expand All@@ -15,7 +15,7 @@ struct Agents<VPN: VPNService, S: Session>: View {
let items = vpn.menuState.sorted
let visibleItems = viewAll ? items[...] : items.prefix(defaultVisibleRows)
ForEach(visibleItems, id: \.id) { agent in
MenuItemView(item: agent, baseAccessURL:session.baseAccessURL!)
MenuItemView(item: agent, baseAccessURL:state.baseAccessURL!)
.padding(.horizontal, Theme.Size.trayMargin)
}
if items.count == 0 {
Expand Down
10 changes: 5 additions & 5 deletionsCoder Desktop/Coder Desktop/Views/AuthButton.swift
View file
Open in desktop
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,23 @@
import SwiftUI

struct AuthButton<VPN: VPNService, S: Session>: View {
@EnvironmentObject varsession: S
struct AuthButton<VPN: VPNService>: View {
@EnvironmentObject varstate: AppState
@EnvironmentObject var vpn: VPN
@Environment(\.openWindow) var openWindow

var body: some View {
Button {
ifsession.hasSession {
ifstate.hasSession {
Task {
await vpn.stop()
session.clear()
state.clearSession()
}
} else {
openWindow(id: .login)
}
} label: {
ButtonRowView {
Text(session.hasSession ? "Sign out" : "Sign in")
Text(state.hasSession ? "Sign out" : "Sign in")
}
}.buttonStyle(.plain)
}
Expand Down
15 changes: 7 additions & 8 deletionsCoder Desktop/Coder Desktop/Views/LoginForm.swift
View file
Open in desktop
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,8 @@
import CoderSDK
import SwiftUI

struct LoginForm<S: Session>: View {
@EnvironmentObject var session: S
@EnvironmentObject var settings: Settings
struct LoginForm: View {
@EnvironmentObject var state: AppState
@Environment(\.dismiss) private var dismiss

@State private var baseAccessURL: String = ""
Expand DownExpand Up@@ -38,7 +37,7 @@ struct LoginForm<S: Session>: View {
}
.animation(.easeInOut, value: currentPage)
.onAppear {
baseAccessURL =session.baseAccessURL?.absoluteString ?? baseAccessURL
baseAccessURL =state.baseAccessURL?.absoluteString ?? baseAccessURL
sessionToken = ""
}
.alert("Error", isPresented: Binding(
Expand DownExpand Up@@ -72,14 +71,14 @@ struct LoginForm<S: Session>: View {
}
loading = true
defer { loading = false }
let client = Client(url: url, token: sessionToken, headers:settings.literalHeaders.map { $0.toSDKHeader() })
let client = Client(url: url, token: sessionToken, headers:state.literalHeaders.map { $0.toSDKHeader() })
do {
_ = try await client.user("me")
} catch {
loginError = .failedAuth(error)
return
}
session.store(baseAccessURL: url, sessionToken: sessionToken)
state.login(baseAccessURL: url, sessionToken: sessionToken)
dismiss()
}

Expand DownExpand Up@@ -219,7 +218,7 @@ enum LoginField: Hashable {

#if DEBUG
#Preview {
LoginForm<PreviewSession>()
.environmentObject(PreviewSession())
LoginForm()
.environmentObject(AppState())
}
#endif
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -2,14 +2,14 @@ import LaunchAtLogin
import SwiftUI

struct GeneralTab: View {
@EnvironmentObject varsettings: Settings
@EnvironmentObject varstate: AppState
var body: some View {
Form {
Section {
LaunchAtLogin.Toggle("Launch at Login")
}
Section {
Toggle(isOn: $settings.stopVPNOnQuit) {
Toggle(isOn: $state.stopVPNOnQuit) {
Text("Stop VPN on Quit")
}
}
Expand Down
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -3,7 +3,7 @@ import SwiftUI
struct LiteralHeaderModal: View {
var existingHeader: LiteralHeader?

@EnvironmentObject varsettings: Settings
@EnvironmentObject varstate: AppState
@Environment(\.dismiss) private var dismiss

@State private var header: String = ""
Expand DownExpand Up@@ -35,11 +35,11 @@ struct LiteralHeaderModal: View {
func submit() {
defer { dismiss() }
if let existingHeader {
settings.literalHeaders.removeAll { $0 == existingHeader }
state.literalHeaders.removeAll { $0 == existingHeader }
}
let newHeader = LiteralHeader(header: header, value: value)
if !settings.literalHeaders.contains(newHeader) {
settings.literalHeaders.append(newHeader)
if !state.literalHeaders.contains(newHeader) {
state.literalHeaders.append(newHeader)
}
}
}
Loading
Loading

[8]ページ先頭

©2009-2025 Movatter.jp