diff --git a/.swiftlint.yml b/.swiftlint.yml
index 22ef035..d609718 100644
--- a/.swiftlint.yml
+++ b/.swiftlint.yml
@@ -46,6 +46,7 @@ opt_in_rules:
- multiline_parameters
- multiline_parameters_brackets
- no_extension_access_modifier
+- no_grouping_extension
- nslocalizedstring_key
- nslocalizedstring_require_bundle
- number_separator
@@ -75,7 +76,9 @@ opt_in_rules:
- sorted_first_last
- sorted_imports
- static_operator
+- strict_fileprivate
- strong_iboutlet
+- switch_case_on_newline
- test_case_accessibility
- toggle_bool
- trailing_closure
@@ -94,5 +97,3 @@ disabled_rules:
- force_try
- nesting
- todo
-- trailing_comma
-- switch_case_on_newline
diff --git a/Apple/App/App.xcconfig b/Apple/App/App.xcconfig
index 4e42ddc..1d63205 100644
--- a/Apple/App/App.xcconfig
+++ b/Apple/App/App.xcconfig
@@ -11,12 +11,7 @@ INFOPLIST_KEY_UIStatusBarStyle[sdk=iphone*] = UIStatusBarStyleDefault
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad[sdk=iphone*] = UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone[sdk=iphone*] = UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight
TARGETED_DEVICE_FAMILY[sdk=iphone*] = 1,2
-EXCLUDED_SOURCE_FILE_NAMES = MainMenu.xib
-EXCLUDED_SOURCE_FILE_NAMES[sdk=macosx*] =
-INFOPLIST_KEY_LSUIElement[sdk=macosx*] = YES
-INFOPLIST_KEY_NSMainNibFile[sdk=macosx*] = MainMenu
-INFOPLIST_KEY_NSPrincipalClass[sdk=macosx*] = NSApplication
INFOPLIST_KEY_LSApplicationCategoryType[sdk=macosx*] = public.app-category.utilities
CODE_SIGN_ENTITLEMENTS = App/App-iOS.entitlements
diff --git a/Apple/App/AppDelegate.swift b/Apple/App/AppDelegate.swift
index 6085d85..f42b52f 100644
--- a/Apple/App/AppDelegate.swift
+++ b/Apple/App/AppDelegate.swift
@@ -3,7 +3,6 @@ import AppKit
import SwiftUI
@MainActor
-@NSApplicationMain
class AppDelegate: NSObject, NSApplicationDelegate {
private let quitItem: NSMenuItem = {
let quitItem = NSMenuItem(
@@ -17,7 +16,7 @@ class AppDelegate: NSObject, NSApplicationDelegate {
}()
private let toggleItem: NSMenuItem = {
- let toggleView = NSHostingView(rootView: MenuItemToggleView())
+ let toggleView = NSHostingView(rootView: MenuItemToggleView(tunnel: BurrowApp.tunnel))
toggleView.frame.size = CGSize(width: 300, height: 32)
toggleView.autoresizingMask = [.width]
diff --git a/Apple/App/Assets.xcassets/HackClub.colorset/Contents.json b/Apple/App/Assets.xcassets/HackClub.colorset/Contents.json
deleted file mode 100644
index 911b4b1..0000000
--- a/Apple/App/Assets.xcassets/HackClub.colorset/Contents.json
+++ /dev/null
@@ -1,20 +0,0 @@
-{
- "colors" : [
- {
- "color" : {
- "color-space" : "srgb",
- "components" : {
- "alpha" : "1.000",
- "blue" : "0x50",
- "green" : "0x37",
- "red" : "0xEC"
- }
- },
- "idiom" : "universal"
- }
- ],
- "info" : {
- "author" : "xcode",
- "version" : 1
- }
-}
diff --git a/Apple/App/Assets.xcassets/HackClub.imageset/Contents.json b/Apple/App/Assets.xcassets/HackClub.imageset/Contents.json
deleted file mode 100644
index ddd0664..0000000
--- a/Apple/App/Assets.xcassets/HackClub.imageset/Contents.json
+++ /dev/null
@@ -1,12 +0,0 @@
-{
- "images" : [
- {
- "filename" : "flag-standalone-wtransparent.pdf",
- "idiom" : "universal"
- }
- ],
- "info" : {
- "author" : "xcode",
- "version" : 1
- }
-}
diff --git a/Apple/App/Assets.xcassets/HackClub.imageset/flag-standalone-wtransparent.pdf b/Apple/App/Assets.xcassets/HackClub.imageset/flag-standalone-wtransparent.pdf
deleted file mode 100644
index 1506fe9..0000000
Binary files a/Apple/App/Assets.xcassets/HackClub.imageset/flag-standalone-wtransparent.pdf and /dev/null differ
diff --git a/Apple/App/Assets.xcassets/WireGuard.colorset/Contents.json b/Apple/App/Assets.xcassets/WireGuard.colorset/Contents.json
deleted file mode 100644
index 092ec69..0000000
--- a/Apple/App/Assets.xcassets/WireGuard.colorset/Contents.json
+++ /dev/null
@@ -1,20 +0,0 @@
-{
- "colors" : [
- {
- "color" : {
- "color-space" : "srgb",
- "components" : {
- "alpha" : "1.000",
- "blue" : "0x1A",
- "green" : "0x17",
- "red" : "0x88"
- }
- },
- "idiom" : "universal"
- }
- ],
- "info" : {
- "author" : "xcode",
- "version" : 1
- }
-}
diff --git a/Apple/App/Assets.xcassets/WireGuard.imageset/Contents.json b/Apple/App/Assets.xcassets/WireGuard.imageset/Contents.json
deleted file mode 100644
index e7fe15a..0000000
--- a/Apple/App/Assets.xcassets/WireGuard.imageset/Contents.json
+++ /dev/null
@@ -1,15 +0,0 @@
-{
- "images" : [
- {
- "filename" : "WireGuard.svg",
- "idiom" : "universal"
- }
- ],
- "info" : {
- "author" : "xcode",
- "version" : 1
- },
- "properties" : {
- "preserves-vector-representation" : true
- }
-}
diff --git a/Apple/App/Assets.xcassets/WireGuard.imageset/WireGuard.svg b/Apple/App/Assets.xcassets/WireGuard.imageset/WireGuard.svg
deleted file mode 100644
index 9520f89..0000000
--- a/Apple/App/Assets.xcassets/WireGuard.imageset/WireGuard.svg
+++ /dev/null
@@ -1,6 +0,0 @@
-
-
\ No newline at end of file
diff --git a/Apple/App/Assets.xcassets/WireGuardTitle.imageset/Contents.json b/Apple/App/Assets.xcassets/WireGuardTitle.imageset/Contents.json
deleted file mode 100644
index 782dd12..0000000
--- a/Apple/App/Assets.xcassets/WireGuardTitle.imageset/Contents.json
+++ /dev/null
@@ -1,21 +0,0 @@
-{
- "images" : [
- {
- "filename" : "WireGuardTitle.svg",
- "idiom" : "universal",
- "scale" : "1x"
- },
- {
- "idiom" : "universal",
- "scale" : "2x"
- },
- {
- "idiom" : "universal",
- "scale" : "3x"
- }
- ],
- "info" : {
- "author" : "xcode",
- "version" : 1
- }
-}
diff --git a/Apple/App/Assets.xcassets/WireGuardTitle.imageset/WireGuardTitle.svg b/Apple/App/Assets.xcassets/WireGuardTitle.imageset/WireGuardTitle.svg
deleted file mode 100644
index 64946da..0000000
--- a/Apple/App/Assets.xcassets/WireGuardTitle.imageset/WireGuardTitle.svg
+++ /dev/null
@@ -1,3 +0,0 @@
-
diff --git a/Apple/App/BurrowApp.swift b/Apple/App/BurrowApp.swift
index 21ebf84..e8aed86 100644
--- a/Apple/App/BurrowApp.swift
+++ b/Apple/App/BurrowApp.swift
@@ -1,13 +1,21 @@
import SwiftUI
-#if !os(macOS)
-@MainActor
@main
+@MainActor
struct BurrowApp: App {
+ static let tunnel = Tunnel { manager, proto in
+ proto.serverAddress = "hackclub.com"
+ manager.localizedDescription = "Burrow"
+ }
+
+ #if os(macOS)
+ @NSApplicationDelegateAdaptor(AppDelegate.self)
+ var delegate
+ #endif
+
var body: some Scene {
WindowGroup {
- BurrowView()
+ TunnelView(tunnel: Self.tunnel)
}
}
}
-#endif
diff --git a/Apple/App/BurrowView.swift b/Apple/App/BurrowView.swift
deleted file mode 100644
index b78b1e1..0000000
--- a/Apple/App/BurrowView.swift
+++ /dev/null
@@ -1,26 +0,0 @@
-import SwiftUI
-
-struct BurrowView: View {
- var body: some View {
- NavigationStack {
- VStack {
- NetworkCarouselView()
- Spacer()
- TunnelStatusView()
- TunnelButton()
- .padding(.bottom)
- }
- .padding()
- .navigationTitle("Networks")
- }
- }
-}
-
-#if DEBUG
-struct NetworkView_Previews: PreviewProvider {
- static var previews: some View {
- BurrowView()
- .environment(\.tunnel, PreviewTunnel())
- }
-}
-#endif
diff --git a/Apple/App/FloatingButtonStyle.swift b/Apple/App/FloatingButtonStyle.swift
deleted file mode 100644
index 53ab5ed..0000000
--- a/Apple/App/FloatingButtonStyle.swift
+++ /dev/null
@@ -1,50 +0,0 @@
-import SwiftUI
-
-struct FloatingButtonStyle: ButtonStyle {
- static let duration = 0.08
-
- var color: Color
- var cornerRadius: CGFloat
-
- func makeBody(configuration: Configuration) -> some View {
- configuration.label
- .font(.headline)
- .foregroundColor(.white)
- .frame(minHeight: 48)
- .padding(.horizontal)
- .background(
- RoundedRectangle(cornerRadius: cornerRadius)
- .fill(
- LinearGradient(
- colors: [
- configuration.isPressed ? color.opacity(0.9) : color.opacity(0.9),
- configuration.isPressed ? color.opacity(0.9) : color
- ],
- startPoint: .init(x: 0.2, y: 0),
- endPoint: .init(x: 0.8, y: 1)
- )
- )
- .background(
- RoundedRectangle(cornerRadius: cornerRadius)
- .fill(configuration.isPressed ? .black : .white)
- )
- )
- .shadow(color: .black.opacity(configuration.isPressed ? 0.0 : 0.1), radius: 2.5, x: 0, y: 2)
- .scaleEffect(configuration.isPressed ? 0.975 : 1.0)
- .padding(.bottom, 2)
- .animation(
- configuration.isPressed ? .easeOut(duration: Self.duration) : .easeIn(duration: Self.duration),
- value: configuration.isPressed
- )
- }
-}
-
-extension ButtonStyle where Self == FloatingButtonStyle {
- static var floating: FloatingButtonStyle {
- floating()
- }
-
- static func floating(color: Color = .accentColor, cornerRadius: CGFloat = 10) -> FloatingButtonStyle {
- FloatingButtonStyle(color: color, cornerRadius: cornerRadius)
- }
-}
diff --git a/Apple/App/MainMenu.xib b/Apple/App/MainMenu.xib
deleted file mode 100644
index 8933f30..0000000
--- a/Apple/App/MainMenu.xib
+++ /dev/null
@@ -1,679 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/Apple/App/Menu/MenuView.swift b/Apple/App/Menu/MenuView.swift
new file mode 100644
index 0000000..eab8da2
--- /dev/null
+++ b/Apple/App/Menu/MenuView.swift
@@ -0,0 +1,60 @@
+//
+// MenuView.swift
+// App
+//
+// Created by Thomas Stubblefield on 5/13/23.
+//
+
+import SwiftUI
+
+struct MenuItemToggleView: View {
+ var tunnel: Tunnel
+
+ var body: some View {
+ HStack {
+ Text("Burrow")
+ .font(.headline)
+ Spacer()
+ Toggle("Burrow", isOn: tunnel.isOn)
+ .labelsHidden()
+ .disabled(tunnel.isDisabled)
+ .toggleStyle(.switch)
+ }
+ .padding(.horizontal, 4)
+ .padding(10)
+ .frame(minWidth: 300, minHeight: 32, maxHeight: 32)
+ }
+}
+
+extension Tunnel {
+ var isDisabled: Bool {
+ switch self.status {
+ case .disconnected, .permissionRequired, .connected:
+ return false
+ case .unknown, .disabled, .connecting, .reasserting, .disconnecting, .invalid, .configurationReadWriteFailed:
+ return true
+ }
+ }
+
+ var isOn: Binding {
+ Binding {
+ switch self.status {
+ case .connecting, .reasserting, .connected:
+ true
+ default:
+ false
+ }
+ } set: { newValue in
+ switch (self.status, newValue) {
+ case (.permissionRequired, true):
+ Task { try await self.configure() }
+ case (.disconnected, true):
+ try? self.start()
+ case (.connected, false):
+ self.stop()
+ default:
+ return
+ }
+ }
+ }
+}
diff --git a/Apple/App/MenuItemToggleView.swift b/Apple/App/MenuItemToggleView.swift
deleted file mode 100644
index 07db51d..0000000
--- a/Apple/App/MenuItemToggleView.swift
+++ /dev/null
@@ -1,64 +0,0 @@
-//
-// MenuItemToggleView.swift
-// App
-//
-// Created by Thomas Stubblefield on 5/13/23.
-//
-
-import SwiftUI
-
-struct MenuItemToggleView: View {
- @Environment(\.tunnel)
- var tunnel: Tunnel
-
- var body: some View {
- HStack {
- VStack(alignment: .leading) {
- Text("Burrow")
- .font(.headline)
- Text(tunnel.status.description)
- .font(.subheadline)
- }
- Spacer()
- Toggle(isOn: tunnel.toggleIsOn) {
- }
- .disabled(tunnel.toggleDisabled)
- .toggleStyle(.switch)
- }
- .accessibilityElement(children: .combine)
- .padding(.horizontal, 4)
- .padding(10)
- .frame(minWidth: 300, minHeight: 32, maxHeight: 32)
- }
-}
-
-extension Tunnel {
- fileprivate var toggleDisabled: Bool {
- switch status {
- case .disconnected, .permissionRequired, .connected, .disconnecting:
- false
- case .unknown, .disabled, .connecting, .reasserting, .invalid, .configurationReadWriteFailed:
- true
- }
- }
-
- var toggleIsOn: Binding {
- Binding {
- switch status {
- case .connecting, .reasserting, .connected:
- true
- default:
- false
- }
- } set: { newValue in
- switch (status, newValue) {
- case (.permissionRequired, true):
- enable()
- case (_, true):
- start()
- case (_, false):
- stop()
- }
- }
- }
-}
diff --git a/Apple/App/NetworkExtensionTunnel.swift b/Apple/App/NetworkExtensionTunnel.swift
deleted file mode 100644
index 08002de..0000000
--- a/Apple/App/NetworkExtensionTunnel.swift
+++ /dev/null
@@ -1,167 +0,0 @@
-import BurrowShared
-import NetworkExtension
-
-@Observable
-class NetworkExtensionTunnel: Tunnel {
- @MainActor private(set) var status: TunnelStatus = .unknown
- private var error: NEVPNError?
-
- private let logger = Logger.logger(for: Tunnel.self)
- private let bundleIdentifier: String
- private var tasks: [Task] = []
-
- // Each manager corresponds to one entry in the Settings app.
- // Our goal is to maintain a single manager, so we create one if none exist and delete any extra.
- private var managers: [NEVPNManager]? {
- didSet { Task { await updateStatus() } }
- }
-
- private var currentStatus: TunnelStatus {
- guard let managers = managers else {
- guard let error = error else {
- return .unknown
- }
-
- switch error.code {
- case .configurationReadWriteFailed:
- return .configurationReadWriteFailed
- default:
- return .unknown
- }
- }
-
- guard let manager = managers.first else {
- return .permissionRequired
- }
-
- guard manager.isEnabled else {
- return .disabled
- }
-
- return manager.connection.tunnelStatus
- }
-
- convenience init() {
- self.init(Constants.networkExtensionBundleIdentifier)
- }
-
- init(_ bundleIdentifier: String) {
- self.bundleIdentifier = bundleIdentifier
-
- let center = NotificationCenter.default
- let configurationChanged = Task { [weak self] in
- for try await _ in center.notifications(named: .NEVPNConfigurationChange).map({ _ in () }) {
- await self?.update()
- }
- }
- let statusChanged = Task { [weak self] in
- for try await _ in center.notifications(named: .NEVPNStatusDidChange).map({ _ in () }) {
- await self?.updateStatus()
- }
- }
- tasks = [configurationChanged, statusChanged]
-
- Task { await update() }
- }
-
- private func update() async {
- do {
- managers = try await NETunnelProviderManager.managers
- await self.updateStatus()
- } catch let vpnError as NEVPNError {
- error = vpnError
- } catch {
- logger.error("Failed to update VPN configurations: \(error)")
- }
- }
-
- private func updateStatus() async {
- await MainActor.run {
- status = currentStatus
- }
- }
-
- func configure() async throws {
- if managers == nil {
- await update()
- }
-
- guard let managers = managers else { return }
-
- if managers.count > 1 {
- try await withThrowingTaskGroup(of: Void.self, returning: Void.self) { group in
- for manager in managers.suffix(from: 1) {
- group.addTask { try await manager.remove() }
- }
- try await group.waitForAll()
- }
- }
-
- guard managers.isEmpty else { return }
-
- let manager = NETunnelProviderManager()
- manager.localizedDescription = "Burrow"
-
- let proto = NETunnelProviderProtocol()
- proto.providerBundleIdentifier = bundleIdentifier
- proto.serverAddress = "hackclub.com"
-
- manager.protocolConfiguration = proto
- try await manager.save()
- }
-
- func start() {
- guard let manager = managers?.first else { return }
- Task {
- do {
- if !manager.isEnabled {
- manager.isEnabled = true
- try await manager.save()
- }
- try manager.connection.startVPNTunnel()
- } catch {
- logger.error("Failed to start: \(error)")
- }
- }
- }
-
- func stop() {
- guard let manager = managers?.first else { return }
- manager.connection.stopVPNTunnel()
- }
-
- func enable() {
- Task {
- do {
- try await configure()
- } catch {
- logger.error("Failed to enable: \(error)")
- }
- }
- }
-
- deinit {
- tasks.forEach { $0.cancel() }
- }
-}
-
-extension NEVPNConnection {
- fileprivate var tunnelStatus: TunnelStatus {
- switch status {
- case .connected:
- .connected(connectedDate!)
- case .connecting:
- .connecting
- case .disconnecting:
- .disconnecting
- case .disconnected:
- .disconnected
- case .reasserting:
- .reasserting
- case .invalid:
- .invalid
- @unknown default:
- .unknown
- }
- }
-}
diff --git a/Apple/App/NetworkView.swift b/Apple/App/NetworkView.swift
deleted file mode 100644
index 290254c..0000000
--- a/Apple/App/NetworkView.swift
+++ /dev/null
@@ -1,88 +0,0 @@
-import SwiftUI
-
-struct NetworkView: View {
- var color: Color
- var content: () -> Content
-
- private var gradient: LinearGradient {
- LinearGradient(
- colors: [
- color.opacity(0.8),
- color
- ],
- startPoint: .init(x: 0.2, y: 0),
- endPoint: .init(x: 0.8, y: 1)
- )
- }
-
- var body: some View {
- content()
- .frame(maxWidth: .infinity, minHeight: 175, maxHeight: 175)
- .background(
- RoundedRectangle(cornerRadius: 10)
- .fill(gradient)
- .background(
- RoundedRectangle(cornerRadius: 10)
- .fill(.white)
- )
- )
- .shadow(color: .black.opacity(0.1), radius: 3.0, x: 0, y: 2)
- }
-}
-
-struct AddNetworkView: View {
- var body: some View {
- Text("Add Network")
- .frame(maxWidth: .infinity, minHeight: 175, maxHeight: 175)
- .background(
- RoundedRectangle(cornerRadius: 10)
- .stroke(style: .init(lineWidth: 2, dash: [6]))
- )
- }
-}
-
-extension NetworkView where Content == AnyView {
- init(network: any Network) {
- color = network.backgroundColor
- content = { AnyView(network.label) }
- }
-}
-
-struct NetworkCarouselView: View {
- var networks: [any Network] = [
- HackClub(id: "1"),
- HackClub(id: "2"),
- WireGuard(id: "4"),
- HackClub(id: "5"),
- ]
-
- var body: some View {
- ScrollView(.horizontal) {
- LazyHStack {
- ForEach(networks, id: \.id) { network in
- NetworkView(network: network)
- .containerRelativeFrame(.horizontal, count: 10, span: 7, spacing: 0, alignment: .center)
- .scrollTransition(.interactive, axis: .horizontal) { content, phase in
- content
- .scaleEffect(1.0 - abs(phase.value) * 0.1)
- }
- }
- AddNetworkView()
- }
- .scrollTargetLayout()
- }
- .scrollClipDisabled()
- .scrollIndicators(.hidden)
- .defaultScrollAnchor(.center)
- .scrollTargetBehavior(.viewAligned)
- .containerRelativeFrame(.horizontal)
- }
-}
-
-#if DEBUG
-struct NetworkCarouselView_Previews: PreviewProvider {
- static var previews: some View {
- NetworkCarouselView()
- }
-}
-#endif
diff --git a/Apple/App/Networks/HackClub.swift b/Apple/App/Networks/HackClub.swift
deleted file mode 100644
index f7df674..0000000
--- a/Apple/App/Networks/HackClub.swift
+++ /dev/null
@@ -1,23 +0,0 @@
-import SwiftUI
-
-struct HackClub: Network {
- var id: String
- var backgroundColor: Color { .init("HackClub") }
-
- var label: some View {
- GeometryReader { reader in
- VStack(alignment: .leading) {
- Image("HackClub")
- .resizable()
- .aspectRatio(contentMode: .fit)
- .frame(height: reader.size.height / 4)
- Spacer()
- Text("@conradev")
- .foregroundStyle(.white)
- .font(.body.monospaced())
- }
- .padding()
- .frame(maxWidth: .infinity)
- }
- }
-}
diff --git a/Apple/App/Networks/Network.swift b/Apple/App/Networks/Network.swift
deleted file mode 100644
index d441d24..0000000
--- a/Apple/App/Networks/Network.swift
+++ /dev/null
@@ -1,10 +0,0 @@
-import SwiftUI
-
-protocol Network {
- associatedtype Label: View
-
- var id: String { get }
- var backgroundColor: Color { get }
-
- var label: Label { get }
-}
diff --git a/Apple/App/Networks/WireGuard.swift b/Apple/App/Networks/WireGuard.swift
deleted file mode 100644
index 499288a..0000000
--- a/Apple/App/Networks/WireGuard.swift
+++ /dev/null
@@ -1,30 +0,0 @@
-import SwiftUI
-
-struct WireGuard: Network {
- var id: String
- var backgroundColor: Color { .init("WireGuard") }
-
- var label: some View {
- GeometryReader { reader in
- VStack(alignment: .leading) {
- HStack {
- Image("WireGuard")
- .resizable()
- .aspectRatio(contentMode: .fit)
- Image("WireGuardTitle")
- .resizable()
- .aspectRatio(contentMode: .fit)
- .frame(width: reader.size.width / 2)
- Spacer()
- }
- .frame(maxWidth: .infinity, maxHeight: reader.size.height / 4)
- Spacer()
- Text("@conradev")
- .foregroundStyle(.white)
- .font(.body.monospaced())
- }
- .padding()
- .frame(maxWidth: .infinity)
- }
- }
-}
diff --git a/Apple/App/Status.swift b/Apple/App/Status.swift
new file mode 100644
index 0000000..c08cdd1
--- /dev/null
+++ b/Apple/App/Status.swift
@@ -0,0 +1,42 @@
+import Foundation
+import NetworkExtension
+
+extension Tunnel {
+ enum Status: CustomStringConvertible, Equatable, Hashable {
+ case unknown
+ case permissionRequired
+ case disabled
+ case connecting
+ case connected(Date)
+ case disconnecting
+ case disconnected
+ case reasserting
+ case invalid
+ case configurationReadWriteFailed
+
+ var description: String {
+ switch self {
+ case .unknown:
+ return "Unknown"
+ case .permissionRequired:
+ return "Permission Required"
+ case .disconnected:
+ return "Disconnected"
+ case .disabled:
+ return "Disabled"
+ case .connecting:
+ return "Connecting"
+ case .connected:
+ return "Connected"
+ case .disconnecting:
+ return "Disconnecting"
+ case .reasserting:
+ return "Reasserting"
+ case .invalid:
+ return "Invalid"
+ case .configurationReadWriteFailed:
+ return "System Error"
+ }
+ }
+ }
+}
diff --git a/Apple/App/Tunnel.swift b/Apple/App/Tunnel.swift
index 8db366f..5542170 100644
--- a/Apple/App/Tunnel.swift
+++ b/Apple/App/Tunnel.swift
@@ -1,50 +1,146 @@
+import BurrowShared
+import NetworkExtension
import SwiftUI
-protocol Tunnel {
- var status: TunnelStatus { get }
-
- func start()
- func stop()
- func enable()
-}
-
-enum TunnelStatus: Equatable, Hashable {
- case unknown
- case permissionRequired
- case disabled
- case connecting
- case connected(Date)
- case disconnecting
- case disconnected
- case reasserting
- case invalid
- case configurationReadWriteFailed
-}
-
-struct TunnelKey: EnvironmentKey {
- static let defaultValue: any Tunnel = NetworkExtensionTunnel()
-}
-
-extension EnvironmentValues {
- var tunnel: any Tunnel {
- get { self[TunnelKey.self] }
- set { self[TunnelKey.self] = newValue }
- }
-}
-
-#if DEBUG
@Observable
-class PreviewTunnel: Tunnel {
- var status: TunnelStatus = .permissionRequired
+class Tunnel {
+ private(set) var status: Status = .unknown
+ private var error: NEVPNError?
- func start() {
- status = .connected(.now)
+ private let logger = Logger.logger(for: Tunnel.self)
+ private let bundleIdentifier: String
+ private let configure: (NETunnelProviderManager, NETunnelProviderProtocol) -> Void
+ private var tasks: [Task] = []
+
+ // Each manager corresponds to one entry in the Settings app.
+ // Our goal is to maintain a single manager, so we create one if none exist and delete extra if there are any.
+ private var managers: [NEVPNManager]? {
+ didSet { status = currentStatus }
}
+
+ private var currentStatus: Status {
+ guard let managers = managers else {
+ guard let error = error else {
+ return .unknown
+ }
+
+ switch error.code {
+ case .configurationReadWriteFailed:
+ return .configurationReadWriteFailed
+ default:
+ return .unknown
+ }
+ }
+
+ guard let manager = managers.first else {
+ return .permissionRequired
+ }
+
+ guard manager.isEnabled else {
+ return .disabled
+ }
+
+ return manager.connection.tunnelStatus
+ }
+
+ convenience init(configure: @escaping (NETunnelProviderManager, NETunnelProviderProtocol) -> Void) {
+ self.init("com.hackclub.burrow.network", configure: configure)
+ }
+
+ init(_ bundleIdentifier: String, configure: @escaping (NETunnelProviderManager, NETunnelProviderProtocol) -> Void) {
+ self.bundleIdentifier = bundleIdentifier
+ self.configure = configure
+
+ let center = NotificationCenter.default
+ let configurationChanged = Task {
+ for try await _ in center.notifications(named: .NEVPNConfigurationChange).map({ _ in () }) {
+ await update()
+ }
+ }
+ let statusChanged = Task {
+ for try await _ in center.notifications(named: .NEVPNStatusDidChange).map({ _ in () }) {
+ await MainActor.run {
+ status = currentStatus
+ }
+ }
+ }
+ tasks = [configurationChanged, statusChanged]
+
+ Task { await update() }
+ }
+
+ private func update() async {
+ do {
+ let updated = try await NETunnelProviderManager.managers
+ await MainActor.run {
+ managers = updated
+ }
+ } catch let vpnError as NEVPNError {
+ error = vpnError
+ } catch {
+ logger.error("Failed to update VPN configurations: \(error)")
+ }
+ }
+
+ func configure() async throws {
+ if managers == nil {
+ await update()
+ }
+
+ guard let managers = managers else { return }
+
+ if managers.count > 1 {
+ try await withThrowingTaskGroup(of: Void.self, returning: Void.self) { group in
+ for manager in managers.suffix(from: 1) {
+ group.addTask { try await manager.remove() }
+ }
+ try await group.waitForAll()
+ }
+ }
+
+ if managers.isEmpty {
+ let manager = NETunnelProviderManager()
+ let proto = NETunnelProviderProtocol()
+ proto.providerBundleIdentifier = bundleIdentifier
+ configure(manager, proto)
+
+ manager.protocolConfiguration = proto
+ try await manager.save()
+ }
+ }
+
+ func start() throws {
+ guard let manager = managers?.first else { return }
+ try manager.connection.startVPNTunnel()
+ }
+
func stop() {
- status = .disconnected
+ guard let manager = managers?.first else { return }
+ manager.connection.stopVPNTunnel()
}
- func enable() {
- status = .disconnected
+
+ deinit {
+ tasks.forEach { $0.cancel() }
+ }
+}
+
+extension NEVPNConnection {
+ var tunnelStatus: Tunnel.Status {
+ switch status {
+ case .connected:
+ .connected(connectedDate!)
+ case .connecting:
+ .connecting
+ case .disconnecting:
+ .disconnecting
+ case .disconnected:
+ .disconnected
+ case .reasserting:
+ .reasserting
+ case .invalid:
+ .invalid
+ @unknown default:
+ .unknown
+ }
}
}
-#endif
diff --git a/Apple/App/TunnelButton.swift b/Apple/App/TunnelButton.swift
deleted file mode 100644
index df8d7e6..0000000
--- a/Apple/App/TunnelButton.swift
+++ /dev/null
@@ -1,61 +0,0 @@
-import SwiftUI
-
-struct TunnelButton: View {
- @Environment(\.tunnel)
- var tunnel: any Tunnel
-
- var body: some View {
- if let action = tunnel.action {
- Button {
- tunnel.perform(action)
- } label: {
- Text(action.description)
- }
- .padding(.horizontal)
- .buttonStyle(.floating)
- }
- }
-}
-
-extension Tunnel {
- fileprivate var action: TunnelButton.Action? {
- switch status {
- case .permissionRequired, .invalid:
- .enable
- case .disabled, .disconnecting, .disconnected:
- .start
- case .connecting, .connected, .reasserting:
- .stop
- case .unknown, .configurationReadWriteFailed:
- nil
- }
- }
-}
-
-extension TunnelButton {
- fileprivate enum Action {
- case enable
- case start
- case stop
- }
-}
-
-extension TunnelButton.Action {
- var description: LocalizedStringKey {
- switch self {
- case .enable: "Enable"
- case .start: "Start"
- case .stop: "Stop"
- }
- }
-}
-
-extension Tunnel {
- fileprivate func perform(_ action: TunnelButton.Action) {
- switch action {
- case .enable: enable()
- case .start: start()
- case .stop: stop()
- }
- }
-}
diff --git a/Apple/App/TunnelStatusView.swift b/Apple/App/TunnelStatusView.swift
deleted file mode 100644
index 3593516..0000000
--- a/Apple/App/TunnelStatusView.swift
+++ /dev/null
@@ -1,37 +0,0 @@
-import SwiftUI
-
-struct TunnelStatusView: View {
- @Environment(\.tunnel)
- var tunnel: any Tunnel
-
- var body: some View {
- Text(tunnel.status.description)
- }
-}
-
-extension TunnelStatus: CustomStringConvertible {
- var description: String {
- switch self {
- case .unknown:
- "Unknown"
- case .permissionRequired:
- "Permission Required"
- case .disconnected:
- "Disconnected"
- case .disabled:
- "Disabled"
- case .connecting:
- "Connecting…"
- case .connected:
- "Connected"
- case .disconnecting:
- "Disconnecting…"
- case .reasserting:
- "Reasserting…"
- case .invalid:
- "Invalid"
- case .configurationReadWriteFailed:
- "System Error"
- }
- }
-}
diff --git a/Apple/App/TunnelView.swift b/Apple/App/TunnelView.swift
new file mode 100644
index 0000000..dd91603
--- /dev/null
+++ b/Apple/App/TunnelView.swift
@@ -0,0 +1,34 @@
+import SwiftUI
+
+struct TunnelView: View {
+ var tunnel: Tunnel
+
+ var body: some View {
+ VStack {
+ Text(verbatim: tunnel.status.description)
+ switch tunnel.status {
+ case .connected:
+ Button("Disconnect", action: stop)
+ case .permissionRequired:
+ Button("Allow", action: configure)
+ case .disconnected:
+ Button("Start", action: start)
+ default:
+ EmptyView()
+ }
+ }
+ .padding()
+ }
+
+ private func start() {
+ try? tunnel.start()
+ }
+
+ private func stop() {
+ tunnel.stop()
+ }
+
+ private func configure() {
+ Task { try await tunnel.configure() }
+ }
+}
diff --git a/Apple/Burrow.xcodeproj/project.pbxproj b/Apple/Burrow.xcodeproj/project.pbxproj
index 8717a30..428d9ab 100644
--- a/Apple/Burrow.xcodeproj/project.pbxproj
+++ b/Apple/Burrow.xcodeproj/project.pbxproj
@@ -9,32 +9,24 @@
/* Begin PBXBuildFile section */
0B28F1562ABF463A000D44B0 /* DataTypes.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0B28F1552ABF463A000D44B0 /* DataTypes.swift */; };
0B46E8E02AC918CA00BA2A3C /* Client.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0B46E8DF2AC918CA00BA2A3C /* Client.swift */; };
- 43AA26D82A10004900F14CE6 /* MenuItemToggleView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43AA26D72A10004900F14CE6 /* MenuItemToggleView.swift */; };
+ 43AA26D82A10004900F14CE6 /* MenuView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43AA26D72A10004900F14CE6 /* MenuView.swift */; };
D00117312B2FFFC900D87C25 /* NWConnection+Async.swift in Sources */ = {isa = PBXBuildFile; fileRef = D00117302B2FFFC900D87C25 /* NWConnection+Async.swift */; };
D00117332B3001A400D87C25 /* NewlineProtocolFramer.swift in Sources */ = {isa = PBXBuildFile; fileRef = D00117322B3001A400D87C25 /* NewlineProtocolFramer.swift */; };
D001173B2B30341C00D87C25 /* Logging.swift in Sources */ = {isa = PBXBuildFile; fileRef = D001173A2B30341C00D87C25 /* Logging.swift */; };
D00117442B30372900D87C25 /* libBurrowShared.a in Frameworks */ = {isa = PBXBuildFile; fileRef = D00117382B30341C00D87C25 /* libBurrowShared.a */; };
D00117452B30372C00D87C25 /* libBurrowShared.a in Frameworks */ = {isa = PBXBuildFile; fileRef = D00117382B30341C00D87C25 /* libBurrowShared.a */; };
D00AA8972A4669BC005C8102 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = D00AA8962A4669BC005C8102 /* AppDelegate.swift */; };
- D01A79312B81630D0024EC91 /* NetworkView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D01A79302B81630D0024EC91 /* NetworkView.swift */; };
D020F65829E4A697002790F6 /* PacketTunnelProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = D020F65729E4A697002790F6 /* PacketTunnelProvider.swift */; };
D020F65D29E4A697002790F6 /* BurrowNetworkExtension.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = D020F65329E4A697002790F6 /* BurrowNetworkExtension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
- D032E6522B8A79C20006B8AD /* HackClub.swift in Sources */ = {isa = PBXBuildFile; fileRef = D032E6512B8A79C20006B8AD /* HackClub.swift */; };
- D032E6542B8A79DA0006B8AD /* WireGuard.swift in Sources */ = {isa = PBXBuildFile; fileRef = D032E6532B8A79DA0006B8AD /* WireGuard.swift */; };
D05B9F7629E39EEC008CB1F9 /* BurrowApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = D05B9F7529E39EEC008CB1F9 /* BurrowApp.swift */; };
- D05B9F7829E39EEC008CB1F9 /* BurrowView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D05B9F7729E39EEC008CB1F9 /* BurrowView.swift */; };
+ D05B9F7829E39EEC008CB1F9 /* TunnelView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D05B9F7729E39EEC008CB1F9 /* TunnelView.swift */; };
D05B9F7A29E39EED008CB1F9 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = D05B9F7929E39EED008CB1F9 /* Assets.xcassets */; };
- D05EF8C82B81818D0017AB4F /* FloatingButtonStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = D05EF8C72B81818D0017AB4F /* FloatingButtonStyle.swift */; };
D08252762B5C9FC4005DA378 /* Constants.swift in Sources */ = {isa = PBXBuildFile; fileRef = D08252752B5C9FC4005DA378 /* Constants.swift */; };
- D09150422B9D2AF700BE3CB0 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = D09150412B9D2AF700BE3CB0 /* MainMenu.xib */; };
D0BCC5FD2A086D4700AD070D /* NetworkExtension+Async.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0BCC5FC2A086D4700AD070D /* NetworkExtension+Async.swift */; };
+ D0BCC5FF2A086E1C00AD070D /* Status.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0BCC5FE2A086E1C00AD070D /* Status.swift */; };
D0BCC6082A0981FE00AD070D /* Tunnel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0B98FC629FDC5B5004E7149 /* Tunnel.swift */; };
D0BCC6092A09A03E00AD070D /* libburrow.a in Frameworks */ = {isa = PBXBuildFile; fileRef = D0BCC6032A09535900AD070D /* libburrow.a */; };
D0BCC60A2A09A0B800AD070D /* build-rust.sh in Resources */ = {isa = PBXBuildFile; fileRef = D0B98FBF29FD8072004E7149 /* build-rust.sh */; };
- D0FAB5922B818A5900F6A84B /* NetworkExtensionTunnel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0FAB5912B818A5900F6A84B /* NetworkExtensionTunnel.swift */; };
- D0FAB5962B818B2900F6A84B /* TunnelButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0FAB5952B818B2900F6A84B /* TunnelButton.swift */; };
- D0FAB5982B818B8200F6A84B /* TunnelStatusView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0FAB5972B818B8200F6A84B /* TunnelStatusView.swift */; };
- D0FAB59A2B818B9600F6A84B /* Network.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0FAB5992B818B9600F6A84B /* Network.swift */; };
/* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
@@ -78,7 +70,7 @@
/* Begin PBXFileReference section */
0B28F1552ABF463A000D44B0 /* DataTypes.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DataTypes.swift; sourceTree = ""; };
0B46E8DF2AC918CA00BA2A3C /* Client.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Client.swift; sourceTree = ""; };
- 43AA26D72A10004900F14CE6 /* MenuItemToggleView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MenuItemToggleView.swift; sourceTree = ""; };
+ 43AA26D72A10004900F14CE6 /* MenuView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MenuView.swift; sourceTree = ""; };
D00117302B2FFFC900D87C25 /* NWConnection+Async.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NWConnection+Async.swift"; sourceTree = ""; };
D00117322B3001A400D87C25 /* NewlineProtocolFramer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NewlineProtocolFramer.swift; sourceTree = ""; };
D00117382B30341C00D87C25 /* libBurrowShared.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libBurrowShared.a; sourceTree = BUILT_PRODUCTS_DIR; };
@@ -86,7 +78,6 @@
D00117412B30347800D87C25 /* module.modulemap */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.module-map"; path = module.modulemap; sourceTree = ""; };
D00117422B30348D00D87C25 /* Shared.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Shared.xcconfig; sourceTree = ""; };
D00AA8962A4669BC005C8102 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; };
- D01A79302B81630D0024EC91 /* NetworkView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkView.swift; sourceTree = ""; };
D020F63D29E4A1FF002790F6 /* Identity.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Identity.xcconfig; sourceTree = ""; };
D020F64029E4A1FF002790F6 /* Compiler.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Compiler.xcconfig; sourceTree = ""; };
D020F64229E4A1FF002790F6 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
@@ -101,26 +92,19 @@
D020F66729E4A95D002790F6 /* NetworkExtension-iOS.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = "NetworkExtension-iOS.entitlements"; sourceTree = ""; };
D020F66829E4AA74002790F6 /* App-iOS.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = "App-iOS.entitlements"; sourceTree = ""; };
D020F66929E4AA74002790F6 /* App-macOS.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = "App-macOS.entitlements"; sourceTree = ""; };
- D032E6512B8A79C20006B8AD /* HackClub.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HackClub.swift; sourceTree = ""; };
- D032E6532B8A79DA0006B8AD /* WireGuard.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WireGuard.swift; sourceTree = ""; };
D05B9F7229E39EEC008CB1F9 /* Burrow.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Burrow.app; sourceTree = BUILT_PRODUCTS_DIR; };
D05B9F7529E39EEC008CB1F9 /* BurrowApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BurrowApp.swift; sourceTree = ""; };
- D05B9F7729E39EEC008CB1F9 /* BurrowView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BurrowView.swift; sourceTree = ""; };
+ D05B9F7729E39EEC008CB1F9 /* TunnelView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TunnelView.swift; sourceTree = ""; };
D05B9F7929E39EED008CB1F9 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; };
- D05EF8C72B81818D0017AB4F /* FloatingButtonStyle.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FloatingButtonStyle.swift; sourceTree = ""; };
D08252742B5C9DEB005DA378 /* Constants.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = Constants.h; sourceTree = ""; };
D08252752B5C9FC4005DA378 /* Constants.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Constants.swift; sourceTree = ""; };
- D09150412B9D2AF700BE3CB0 /* MainMenu.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = MainMenu.xib; sourceTree = ""; };
D0B98FBF29FD8072004E7149 /* build-rust.sh */ = {isa = PBXFileReference; lastKnownFileType = text.script.sh; path = "build-rust.sh"; sourceTree = ""; };
D0B98FC629FDC5B5004E7149 /* Tunnel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Tunnel.swift; sourceTree = ""; };
D0B98FD829FDDB6F004E7149 /* libburrow.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = libburrow.h; sourceTree = ""; };
D0B98FDC29FDDDCF004E7149 /* module.modulemap */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.module-map"; path = module.modulemap; sourceTree = ""; };
D0BCC5FC2A086D4700AD070D /* NetworkExtension+Async.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NetworkExtension+Async.swift"; sourceTree = ""; };
+ D0BCC5FE2A086E1C00AD070D /* Status.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Status.swift; sourceTree = ""; };
D0BCC6032A09535900AD070D /* libburrow.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libburrow.a; sourceTree = BUILT_PRODUCTS_DIR; };
- D0FAB5912B818A5900F6A84B /* NetworkExtensionTunnel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkExtensionTunnel.swift; sourceTree = ""; };
- D0FAB5952B818B2900F6A84B /* TunnelButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TunnelButton.swift; sourceTree = ""; };
- D0FAB5972B818B8200F6A84B /* TunnelStatusView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TunnelStatusView.swift; sourceTree = ""; };
- D0FAB5992B818B9600F6A84B /* Network.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Network.swift; sourceTree = ""; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
@@ -151,6 +135,14 @@
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
+ 43AA26D62A0FFFD000F14CE6 /* Menu */ = {
+ isa = PBXGroup;
+ children = (
+ 43AA26D72A10004900F14CE6 /* MenuView.swift */,
+ );
+ path = Menu;
+ sourceTree = "";
+ };
D00117392B30341C00D87C25 /* Shared */ = {
isa = PBXGroup;
children = (
@@ -207,16 +199,6 @@
path = NetworkExtension;
sourceTree = "";
};
- D032E64D2B8A69C90006B8AD /* Networks */ = {
- isa = PBXGroup;
- children = (
- D0FAB5992B818B9600F6A84B /* Network.swift */,
- D032E6512B8A79C20006B8AD /* HackClub.swift */,
- D032E6532B8A79DA0006B8AD /* WireGuard.swift */,
- );
- path = Networks;
- sourceTree = "";
- };
D05B9F6929E39EEC008CB1F9 = {
isa = PBXGroup;
children = (
@@ -242,20 +224,14 @@
D05B9F7429E39EEC008CB1F9 /* App */ = {
isa = PBXGroup;
children = (
+ 43AA26D62A0FFFD000F14CE6 /* Menu */,
D05B9F7529E39EEC008CB1F9 /* BurrowApp.swift */,
D00AA8962A4669BC005C8102 /* AppDelegate.swift */,
- 43AA26D72A10004900F14CE6 /* MenuItemToggleView.swift */,
- D05B9F7729E39EEC008CB1F9 /* BurrowView.swift */,
- D01A79302B81630D0024EC91 /* NetworkView.swift */,
- D032E64D2B8A69C90006B8AD /* Networks */,
- D0FAB5972B818B8200F6A84B /* TunnelStatusView.swift */,
- D0FAB5952B818B2900F6A84B /* TunnelButton.swift */,
+ D05B9F7729E39EEC008CB1F9 /* TunnelView.swift */,
D0B98FC629FDC5B5004E7149 /* Tunnel.swift */,
- D0FAB5912B818A5900F6A84B /* NetworkExtensionTunnel.swift */,
+ D0BCC5FE2A086E1C00AD070D /* Status.swift */,
D0BCC5FC2A086D4700AD070D /* NetworkExtension+Async.swift */,
- D05EF8C72B81818D0017AB4F /* FloatingButtonStyle.swift */,
D05B9F7929E39EED008CB1F9 /* Assets.xcassets */,
- D09150412B9D2AF700BE3CB0 /* MainMenu.xib */,
D020F66829E4AA74002790F6 /* App-iOS.entitlements */,
D020F66929E4AA74002790F6 /* App-macOS.entitlements */,
D020F64929E4A34B002790F6 /* App.xcconfig */,
@@ -343,7 +319,7 @@
attributes = {
BuildIndependentTargetsInParallel = 1;
LastSwiftUpdateCheck = 1510;
- LastUpgradeCheck = 1520;
+ LastUpgradeCheck = 1430;
TargetAttributes = {
D00117372B30341C00D87C25 = {
CreatedOnToolsVersion = 15.1;
@@ -393,7 +369,6 @@
buildActionMask = 2147483647;
files = (
D05B9F7A29E39EED008CB1F9 /* Assets.xcassets in Resources */,
- D09150422B9D2AF700BE3CB0 /* MainMenu.xib in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -448,19 +423,12 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
- D0FAB59A2B818B9600F6A84B /* Network.swift in Sources */,
D0BCC6082A0981FE00AD070D /* Tunnel.swift in Sources */,
- D0FAB5982B818B8200F6A84B /* TunnelStatusView.swift in Sources */,
- 43AA26D82A10004900F14CE6 /* MenuItemToggleView.swift in Sources */,
- D05B9F7829E39EEC008CB1F9 /* BurrowView.swift in Sources */,
- D0FAB5922B818A5900F6A84B /* NetworkExtensionTunnel.swift in Sources */,
- D0FAB5962B818B2900F6A84B /* TunnelButton.swift in Sources */,
+ 43AA26D82A10004900F14CE6 /* MenuView.swift in Sources */,
+ D05B9F7829E39EEC008CB1F9 /* TunnelView.swift in Sources */,
+ D0BCC5FF2A086E1C00AD070D /* Status.swift in Sources */,
D00AA8972A4669BC005C8102 /* AppDelegate.swift in Sources */,
- D05EF8C82B81818D0017AB4F /* FloatingButtonStyle.swift in Sources */,
- D032E6522B8A79C20006B8AD /* HackClub.swift in Sources */,
D05B9F7629E39EEC008CB1F9 /* BurrowApp.swift in Sources */,
- D01A79312B81630D0024EC91 /* NetworkView.swift in Sources */,
- D032E6542B8A79DA0006B8AD /* WireGuard.swift in Sources */,
D0BCC5FD2A086D4700AD070D /* NetworkExtension+Async.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
@@ -600,8 +568,8 @@
isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/realm/SwiftLint.git";
requirement = {
- branch = main;
- kind = branch;
+ kind = upToNextMajorVersion;
+ minimumVersion = 0.54.0;
};
};
/* End XCRemoteSwiftPackageReference section */
diff --git a/Apple/Burrow.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/Apple/Burrow.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved
index 9378372..7522840 100644
--- a/Apple/Burrow.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved
+++ b/Apple/Burrow.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved
@@ -32,8 +32,8 @@
"kind" : "remoteSourceControl",
"location" : "https://github.com/apple/swift-argument-parser.git",
"state" : {
- "revision" : "8f4d2753f0e4778c76d5f05ad16c74f707390531",
- "version" : "1.2.3"
+ "revision" : "fee6933f37fde9a5e12a1e4aeaa93fe60116ff2a",
+ "version" : "1.2.2"
}
},
{
@@ -41,8 +41,8 @@
"kind" : "remoteSourceControl",
"location" : "https://github.com/apple/swift-syntax.git",
"state" : {
- "revision" : "64889f0c732f210a935a0ad7cda38f77f876262d",
- "version" : "509.1.1"
+ "revision" : "6ad4ea24b01559dde0773e3d091f1b9e36175036",
+ "version" : "509.0.2"
}
},
{
@@ -50,8 +50,8 @@
"kind" : "remoteSourceControl",
"location" : "https://github.com/realm/SwiftLint.git",
"state" : {
- "branch" : "main",
- "revision" : "7595ad3fafc1a31086dc40ba01fd898bf6b42d5f"
+ "revision" : "f17a4f9dfb6a6afb0408426354e4180daaf49cee",
+ "version" : "0.54.0"
}
},
{
@@ -68,8 +68,8 @@
"kind" : "remoteSourceControl",
"location" : "https://github.com/drmohundro/SWXMLHash.git",
"state" : {
- "revision" : "a853604c9e9a83ad9954c7e3d2a565273982471f",
- "version" : "7.0.2"
+ "revision" : "4d0f62f561458cbe1f732171e625f03195151b60",
+ "version" : "7.0.1"
}
},
{
diff --git a/Apple/Burrow.xcodeproj/xcshareddata/xcschemes/App.xcscheme b/Apple/Burrow.xcodeproj/xcshareddata/xcschemes/App.xcscheme
index 670823d..c63f8e6 100644
--- a/Apple/Burrow.xcodeproj/xcshareddata/xcschemes/App.xcscheme
+++ b/Apple/Burrow.xcodeproj/xcshareddata/xcschemes/App.xcscheme
@@ -1,6 +1,6 @@