Skip to content

Commit

Permalink
feat: support user-supplied literal headers
Browse files Browse the repository at this point in the history
  • Loading branch information
ethanndickson committed Jan 17, 2025
1 parent 5d97953 commit dc8972e
Show file tree
Hide file tree
Showing 17 changed files with 472 additions and 64 deletions.
174 changes: 139 additions & 35 deletions Coder Desktop/Coder Desktop.xcodeproj/project.pbxproj

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"originHash" : "ec40e522ec1a2416e8e8f5cbe97424ab3e4a614e6ef453c10ea28e84e88b6771",
"originHash" : "b52ef58779afac669f0b78fbf402855ebb45d016ab69ee39b5470c9442c12823",
"pins" : [
{
"identity" : "fluid-menu-bar-extra",
Expand All @@ -18,6 +18,15 @@
"revision" : "e0c7eebc5a4465a3c4680764f26b7a61f567cdaf"
}
},
{
"identity" : "launchatlogin-modern",
"kind" : "remoteSourceControl",
"location" : "https://github.com/sindresorhus/LaunchAtLogin-modern",
"state" : {
"revision" : "a04ec1c363be3627734f6dad757d82f5d4fa8fcc",
"version" : "1.1.0"
}
},
{
"identity" : "mocker",
"kind" : "remoteSourceControl",
Expand All @@ -27,6 +36,15 @@
"version" : "3.0.2"
}
},
{
"identity" : "settingsaccess",
"kind" : "remoteSourceControl",
"location" : "https://github.com/orchetect/SettingsAccess",
"state" : {
"revision" : "08e80c35501f273afa2f5d6f737429bbe395ff81",
"version" : "2.1.0"
}
},
{
"identity" : "swift-protobuf",
"kind" : "remoteSourceControl",
Expand All @@ -41,8 +59,8 @@
"kind" : "remoteSourceControl",
"location" : "https://github.com/SimplyDanny/SwiftLintPlugins",
"state" : {
"revision" : "f9731bef175c3eea3a0ca960f1be78fcc2bc7853",
"version" : "0.57.1"
"revision" : "fac0c3d3ac69b15ea5382275dbbd5e583a2e05fa",
"version" : "0.58.0"
}
},
{
Expand Down
10 changes: 1 addition & 9 deletions Coder Desktop/Coder Desktop/About.swift
Original file line number Diff line number Diff line change
Expand Up @@ -32,15 +32,7 @@ enum About {

@MainActor
static func open() {
#if compiler(>=5.9) && canImport(AppKit)
if #available(macOS 14, *) {
NSApp.activate()
} else {
NSApp.activate(ignoringOtherApps: true)
}
#else
NSApp.activate(ignoringOtherApps: true)
#endif
appActivate()
NSApp.orderFrontStandardAboutPanel(options: [
.credits: credits,
])
Expand Down
24 changes: 23 additions & 1 deletion Coder Desktop/Coder Desktop/Coder_DesktopApp.swift
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import FluidMenuBarExtra
import SwiftData
import SwiftUI

@main
Expand All @@ -14,6 +15,11 @@ struct DesktopApp: App {
LoginForm<PreviewSession>().environmentObject(appDelegate.session)
}
.windowResizability(.contentSize)
SwiftUI.Settings { SettingsView<PreviewVPN>()
.environmentObject(appDelegate.vpn)
.environmentObject(appDelegate.settings)
}
.windowResizability(.contentSize)
}
}

Expand All @@ -22,10 +28,12 @@ class AppDelegate: NSObject, NSApplicationDelegate {
private var menuBarExtra: FluidMenuBarExtra?
let vpn: PreviewVPN
let session: PreviewSession
let settings: Settings

override init() {
// TODO: Replace with real implementations
// TODO: Replace with real implementation
vpn = PreviewVPN()
settings = Settings()
session = PreviewSession()
}

Expand All @@ -34,10 +42,24 @@ class AppDelegate: NSObject, NSApplicationDelegate {
VPNMenu<PreviewVPN, PreviewSession>().frame(width: 256)
.environmentObject(self.vpn)
.environmentObject(self.session)
.environmentObject(self.settings)
}
}

func applicationShouldTerminateAfterLastWindowClosed(_: NSApplication) -> Bool {
false
}
}

@MainActor
func appActivate() {
#if compiler(>=5.9) && canImport(AppKit)
if #available(macOS 14, *) {
NSApp.activate()
} else {
NSApp.activate(ignoringOtherApps: true)
}
#else
NSApp.activate(ignoringOtherApps: true)
#endif
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import CoderSDK
import Foundation
import KeychainAccess
import NetworkExtension
import SwiftUI

protocol Session: ObservableObject {
var hasSession: Bool { get }
Expand Down Expand Up @@ -89,3 +91,47 @@ class SecureSession: ObservableObject, Session {
static let sessionToken = "sessionToken"
}
}

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

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

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

enum Keys {
static let useLiteralHeaders = "UseLiteralHeaders"
static let literalHeaders = "LiteralHeaders"
}
}

struct LiteralHeader: Hashable, Identifiable, Equatable, Codable {
var header: String
var value: String
var id: String {
"\(header):\(value)"
}

init(header: String, value: String) {
self.header = header
self.value = value
}
}

extension LiteralHeader {
func toSDKHeader() -> HTTPHeader {
return .init(header: header, value: value)
}
}
4 changes: 3 additions & 1 deletion Coder Desktop/Coder Desktop/Views/LoginForm.swift
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import CoderSDK
import SwiftData
import SwiftUI

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

@State private var baseAccessURL: String = ""
Expand Down Expand Up @@ -68,7 +70,7 @@ struct LoginForm<S: Session>: View {
}
loading = true
defer { loading = false }
let client = Client(url: url, token: sessionToken)
let client = Client(url: url, token: sessionToken, headers: settings.literalHeaders.map { $0.toSDKHeader() })
do {
_ = try await client.user("me")
} catch {
Expand Down
16 changes: 16 additions & 0 deletions Coder Desktop/Coder Desktop/Views/Settings/GeneralTab.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import LaunchAtLogin
import SwiftUI

struct GeneralTab: View {
var body: some View {
Form {
Section {
LaunchAtLogin.Toggle("Launch at Login")
}
}.formStyle(.grouped)
}
}

#Preview {
GeneralTab()
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import SwiftData
import SwiftUI

struct LiteralHeaderModal: View {
var existingHeader: LiteralHeader?

@EnvironmentObject var settings: Settings
@Environment(\.dismiss) private var dismiss

@State private var header: String = ""
@State private var value: String = ""

var body: some View {
VStack(spacing: 0) {
Form {
Section {
TextField("Header", text: $header)
TextField("Value", text: $value)
}
}.formStyle(.grouped).scrollDisabled(true).padding(.horizontal)
Divider()
HStack {
Spacer()
Button("Cancel", action: { dismiss() }).keyboardShortcut(.cancelAction)
Button(existingHeader == nil ? "Add" : "Save", action: submit)
.keyboardShortcut(.defaultAction)
}.padding(20)
}.onAppear {
if let existingHeader {
self.header = existingHeader.header
self.value = existingHeader.value
}
}
}

func submit() {
defer { dismiss() }
if let existingHeader {
settings.literalHeaders.removeAll { $0 == existingHeader }
}
let newHeader = LiteralHeader(header: header, value: value)
if !settings.literalHeaders.contains(newHeader) {
settings.literalHeaders.append(newHeader)
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import SwiftData
import SwiftUI

struct LiteralHeadersSection<VPN: VPNService>: View {
@EnvironmentObject var vpn: VPN
@EnvironmentObject var settings: Settings

@State private var selectedHeader: LiteralHeader.ID?
@State private var editingHeader: LiteralHeader?
@State private var addingNewHeader = false

let inspection = Inspection<Self>()

var body: some View {
Section {
Toggle(isOn: settings.$useLiteralHeaders) {
Text("HTTP Headers")
Text("When enabled, these headers will be included on all outgoing HTTP requests.")
if vpn.state != .disabled { Text("Cannot be modified while Coder VPN is enabled.") }
}
.controlSize(.large)

Table(settings.literalHeaders, selection: $selectedHeader) {
TableColumn("Header", value: \.header)
TableColumn("Value", value: \.value)
}.opacity(settings.useLiteralHeaders ? 1 : 0.5)
.frame(minWidth: 400, minHeight: 200)
.padding(.bottom, 25)
.overlay(alignment: .bottom) {
VStack(alignment: .leading, spacing: 0) {
Divider()
HStack(spacing: 0) {
Button {
addingNewHeader = true
} label: {
Image(systemName: "plus")
.frame(width: 24, height: 24)
}
Divider()
Button {
settings.literalHeaders.removeAll { $0.id == selectedHeader }
selectedHeader = nil
} label: {
Image(systemName: "minus")
.frame(width: 24, height: 24)
}.disabled(selectedHeader == nil)
}
.buttonStyle(.borderless)
}
.background(.primary.opacity(0.04))
.fixedSize(horizontal: false, vertical: true)
}
.background(.primary.opacity(0.04))
.contextMenu(forSelectionType: LiteralHeader.ID.self, menu: { _ in },
primaryAction: { selectedHeaders in
if let firstHeader = selectedHeaders.first {
editingHeader = settings.literalHeaders.first(where: { $0.id == firstHeader })
}
})
.disabled(!settings.useLiteralHeaders)
}
.sheet(isPresented: $addingNewHeader) {
LiteralHeaderModal()
}
.sheet(item: $editingHeader) { header in
LiteralHeaderModal(existingHeader: header)
}.onTapGesture {
selectedHeader = nil
}.disabled(vpn.state != .disabled)
.onReceive(inspection.notice) { self.inspection.visit(self, $0) } // ViewInspector
}
}
15 changes: 15 additions & 0 deletions Coder Desktop/Coder Desktop/Views/Settings/NetworkTab.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import SwiftData
import SwiftUI

struct NetworkTab<VPN: VPNService>: View {
var body: some View {
Form {
LiteralHeadersSection<VPN>()
}
.formStyle(.grouped)
}
}

#Preview {
NetworkTab<PreviewVPN>()
}
26 changes: 26 additions & 0 deletions Coder Desktop/Coder Desktop/Views/Settings/Settings.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import SwiftUI

struct SettingsView<VPN: VPNService>: View {
@AppStorage("SettingsSelectedIndex") private var selection: SettingsTab = .general

var body: some View {
TabView(selection: $selection) {
GeneralTab()
.tabItem {
Label("General", systemImage: "gearshape")
}.tag(SettingsTab.general)
NetworkTab<VPN>()
.tabItem {
Label("Network", systemImage: "dot.radiowaves.left.and.right")
}.tag(SettingsTab.network)
}.frame(width: 600)
.frame(maxHeight: 500)
.scrollContentBackground(.hidden)
.fixedSize()
}
}

enum SettingsTab: Int {
case general
case network
}
1 change: 1 addition & 0 deletions Coder Desktop/Coder Desktop/Views/Util.swift
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import Combine
import SwiftUI

// This is required for inspecting stateful views
final class Inspection<V> {
Expand Down
Loading

0 comments on commit dc8972e

Please sign in to comment.