diff --git a/.swiftlint.yml b/.swiftlint.yml
index d609718..22ef035 100644
--- a/.swiftlint.yml
+++ b/.swiftlint.yml
@@ -46,7 +46,6 @@ opt_in_rules:
- multiline_parameters
- multiline_parameters_brackets
- no_extension_access_modifier
-- no_grouping_extension
- nslocalizedstring_key
- nslocalizedstring_require_bundle
- number_separator
@@ -76,9 +75,7 @@ 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
@@ -97,3 +94,5 @@ 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 1d63205..4e42ddc 100644
--- a/Apple/App/App.xcconfig
+++ b/Apple/App/App.xcconfig
@@ -11,7 +11,12 @@ 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 f42b52f..6085d85 100644
--- a/Apple/App/AppDelegate.swift
+++ b/Apple/App/AppDelegate.swift
@@ -3,6 +3,7 @@ import AppKit
import SwiftUI
@MainActor
+@NSApplicationMain
class AppDelegate: NSObject, NSApplicationDelegate {
private let quitItem: NSMenuItem = {
let quitItem = NSMenuItem(
@@ -16,7 +17,7 @@ class AppDelegate: NSObject, NSApplicationDelegate {
}()
private let toggleItem: NSMenuItem = {
- let toggleView = NSHostingView(rootView: MenuItemToggleView(tunnel: BurrowApp.tunnel))
+ let toggleView = NSHostingView(rootView: MenuItemToggleView())
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
new file mode 100644
index 0000000..911b4b1
--- /dev/null
+++ b/Apple/App/Assets.xcassets/HackClub.colorset/Contents.json
@@ -0,0 +1,20 @@
+{
+ "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
new file mode 100644
index 0000000..ddd0664
--- /dev/null
+++ b/Apple/App/Assets.xcassets/HackClub.imageset/Contents.json
@@ -0,0 +1,12 @@
+{
+ "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
new file mode 100644
index 0000000..1506fe9
Binary files /dev/null and b/Apple/App/Assets.xcassets/HackClub.imageset/flag-standalone-wtransparent.pdf differ
diff --git a/Apple/App/Assets.xcassets/WireGuard.colorset/Contents.json b/Apple/App/Assets.xcassets/WireGuard.colorset/Contents.json
new file mode 100644
index 0000000..092ec69
--- /dev/null
+++ b/Apple/App/Assets.xcassets/WireGuard.colorset/Contents.json
@@ -0,0 +1,20 @@
+{
+ "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
new file mode 100644
index 0000000..e7fe15a
--- /dev/null
+++ b/Apple/App/Assets.xcassets/WireGuard.imageset/Contents.json
@@ -0,0 +1,15 @@
+{
+ "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
new file mode 100644
index 0000000..9520f89
--- /dev/null
+++ b/Apple/App/Assets.xcassets/WireGuard.imageset/WireGuard.svg
@@ -0,0 +1,6 @@
+
+
\ 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
new file mode 100644
index 0000000..782dd12
--- /dev/null
+++ b/Apple/App/Assets.xcassets/WireGuardTitle.imageset/Contents.json
@@ -0,0 +1,21 @@
+{
+ "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
new file mode 100644
index 0000000..64946da
--- /dev/null
+++ b/Apple/App/Assets.xcassets/WireGuardTitle.imageset/WireGuardTitle.svg
@@ -0,0 +1,3 @@
+
diff --git a/Apple/App/BurrowApp.swift b/Apple/App/BurrowApp.swift
index e8aed86..21ebf84 100644
--- a/Apple/App/BurrowApp.swift
+++ b/Apple/App/BurrowApp.swift
@@ -1,21 +1,13 @@
import SwiftUI
-@main
+#if !os(macOS)
@MainActor
+@main
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 {
- TunnelView(tunnel: Self.tunnel)
+ BurrowView()
}
}
}
+#endif
diff --git a/Apple/App/BurrowView.swift b/Apple/App/BurrowView.swift
new file mode 100644
index 0000000..b78b1e1
--- /dev/null
+++ b/Apple/App/BurrowView.swift
@@ -0,0 +1,26 @@
+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
new file mode 100644
index 0000000..53ab5ed
--- /dev/null
+++ b/Apple/App/FloatingButtonStyle.swift
@@ -0,0 +1,50 @@
+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
new file mode 100644
index 0000000..8933f30
--- /dev/null
+++ b/Apple/App/MainMenu.xib
@@ -0,0 +1,679 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Apple/App/Menu/MenuView.swift b/Apple/App/Menu/MenuView.swift
deleted file mode 100644
index eab8da2..0000000
--- a/Apple/App/Menu/MenuView.swift
+++ /dev/null
@@ -1,60 +0,0 @@
-//
-// 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
new file mode 100644
index 0000000..07db51d
--- /dev/null
+++ b/Apple/App/MenuItemToggleView.swift
@@ -0,0 +1,64 @@
+//
+// 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
new file mode 100644
index 0000000..08002de
--- /dev/null
+++ b/Apple/App/NetworkExtensionTunnel.swift
@@ -0,0 +1,167 @@
+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
new file mode 100644
index 0000000..290254c
--- /dev/null
+++ b/Apple/App/NetworkView.swift
@@ -0,0 +1,88 @@
+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
new file mode 100644
index 0000000..f7df674
--- /dev/null
+++ b/Apple/App/Networks/HackClub.swift
@@ -0,0 +1,23 @@
+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
new file mode 100644
index 0000000..d441d24
--- /dev/null
+++ b/Apple/App/Networks/Network.swift
@@ -0,0 +1,10 @@
+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
new file mode 100644
index 0000000..499288a
--- /dev/null
+++ b/Apple/App/Networks/WireGuard.swift
@@ -0,0 +1,30 @@
+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
deleted file mode 100644
index c08cdd1..0000000
--- a/Apple/App/Status.swift
+++ /dev/null
@@ -1,42 +0,0 @@
-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 5542170..8db366f 100644
--- a/Apple/App/Tunnel.swift
+++ b/Apple/App/Tunnel.swift
@@ -1,146 +1,50 @@
-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 Tunnel {
- private(set) var status: Status = .unknown
- private var error: NEVPNError?
+class PreviewTunnel: Tunnel {
+ var status: TunnelStatus = .permissionRequired
- 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 }
+ func start() {
+ status = .connected(.now)
}
-
- 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() {
- guard let manager = managers?.first else { return }
- manager.connection.stopVPNTunnel()
+ 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
- }
+ func enable() {
+ status = .disconnected
}
}
+#endif
diff --git a/Apple/App/TunnelButton.swift b/Apple/App/TunnelButton.swift
new file mode 100644
index 0000000..df8d7e6
--- /dev/null
+++ b/Apple/App/TunnelButton.swift
@@ -0,0 +1,61 @@
+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
new file mode 100644
index 0000000..3593516
--- /dev/null
+++ b/Apple/App/TunnelStatusView.swift
@@ -0,0 +1,37 @@
+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
deleted file mode 100644
index dd91603..0000000
--- a/Apple/App/TunnelView.swift
+++ /dev/null
@@ -1,34 +0,0 @@
-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 6127e1a..8717a30 100644
--- a/Apple/Burrow.xcodeproj/project.pbxproj
+++ b/Apple/Burrow.xcodeproj/project.pbxproj
@@ -9,24 +9,32 @@
/* 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 /* MenuView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43AA26D72A10004900F14CE6 /* MenuView.swift */; };
+ 43AA26D82A10004900F14CE6 /* MenuItemToggleView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43AA26D72A10004900F14CE6 /* MenuItemToggleView.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 /* TunnelView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D05B9F7729E39EEC008CB1F9 /* TunnelView.swift */; };
+ D05B9F7829E39EEC008CB1F9 /* BurrowView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D05B9F7729E39EEC008CB1F9 /* BurrowView.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 */
@@ -70,7 +78,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 /* MenuView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MenuView.swift; sourceTree = ""; };
+ 43AA26D72A10004900F14CE6 /* MenuItemToggleView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MenuItemToggleView.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; };
@@ -78,6 +86,7 @@
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 = ""; };
@@ -92,19 +101,26 @@
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 /* TunnelView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TunnelView.swift; sourceTree = ""; };
+ D05B9F7729E39EEC008CB1F9 /* BurrowView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BurrowView.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 */
@@ -135,14 +151,6 @@
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
- 43AA26D62A0FFFD000F14CE6 /* Menu */ = {
- isa = PBXGroup;
- children = (
- 43AA26D72A10004900F14CE6 /* MenuView.swift */,
- );
- path = Menu;
- sourceTree = "";
- };
D00117392B30341C00D87C25 /* Shared */ = {
isa = PBXGroup;
children = (
@@ -199,6 +207,16 @@
path = NetworkExtension;
sourceTree = "";
};
+ D032E64D2B8A69C90006B8AD /* Networks */ = {
+ isa = PBXGroup;
+ children = (
+ D0FAB5992B818B9600F6A84B /* Network.swift */,
+ D032E6512B8A79C20006B8AD /* HackClub.swift */,
+ D032E6532B8A79DA0006B8AD /* WireGuard.swift */,
+ );
+ path = Networks;
+ sourceTree = "";
+ };
D05B9F6929E39EEC008CB1F9 = {
isa = PBXGroup;
children = (
@@ -224,14 +242,20 @@
D05B9F7429E39EEC008CB1F9 /* App */ = {
isa = PBXGroup;
children = (
- 43AA26D62A0FFFD000F14CE6 /* Menu */,
D05B9F7529E39EEC008CB1F9 /* BurrowApp.swift */,
D00AA8962A4669BC005C8102 /* AppDelegate.swift */,
- D05B9F7729E39EEC008CB1F9 /* TunnelView.swift */,
+ 43AA26D72A10004900F14CE6 /* MenuItemToggleView.swift */,
+ D05B9F7729E39EEC008CB1F9 /* BurrowView.swift */,
+ D01A79302B81630D0024EC91 /* NetworkView.swift */,
+ D032E64D2B8A69C90006B8AD /* Networks */,
+ D0FAB5972B818B8200F6A84B /* TunnelStatusView.swift */,
+ D0FAB5952B818B2900F6A84B /* TunnelButton.swift */,
D0B98FC629FDC5B5004E7149 /* Tunnel.swift */,
- D0BCC5FE2A086E1C00AD070D /* Status.swift */,
+ D0FAB5912B818A5900F6A84B /* NetworkExtensionTunnel.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 */,
@@ -369,6 +393,7 @@
buildActionMask = 2147483647;
files = (
D05B9F7A29E39EED008CB1F9 /* Assets.xcassets in Resources */,
+ D09150422B9D2AF700BE3CB0 /* MainMenu.xib in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -423,12 +448,19 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
+ D0FAB59A2B818B9600F6A84B /* Network.swift in Sources */,
D0BCC6082A0981FE00AD070D /* Tunnel.swift in Sources */,
- 43AA26D82A10004900F14CE6 /* MenuView.swift in Sources */,
- D05B9F7829E39EEC008CB1F9 /* TunnelView.swift in Sources */,
- D0BCC5FF2A086E1C00AD070D /* Status.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 */,
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;
@@ -568,8 +600,8 @@
isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/realm/SwiftLint.git";
requirement = {
- kind = upToNextMajorVersion;
- minimumVersion = 0.54.0;
+ branch = main;
+ kind = branch;
};
};
/* 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 7522840..9378372 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" : "fee6933f37fde9a5e12a1e4aeaa93fe60116ff2a",
- "version" : "1.2.2"
+ "revision" : "8f4d2753f0e4778c76d5f05ad16c74f707390531",
+ "version" : "1.2.3"
}
},
{
@@ -41,8 +41,8 @@
"kind" : "remoteSourceControl",
"location" : "https://github.com/apple/swift-syntax.git",
"state" : {
- "revision" : "6ad4ea24b01559dde0773e3d091f1b9e36175036",
- "version" : "509.0.2"
+ "revision" : "64889f0c732f210a935a0ad7cda38f77f876262d",
+ "version" : "509.1.1"
}
},
{
@@ -50,8 +50,8 @@
"kind" : "remoteSourceControl",
"location" : "https://github.com/realm/SwiftLint.git",
"state" : {
- "revision" : "f17a4f9dfb6a6afb0408426354e4180daaf49cee",
- "version" : "0.54.0"
+ "branch" : "main",
+ "revision" : "7595ad3fafc1a31086dc40ba01fd898bf6b42d5f"
}
},
{
@@ -68,8 +68,8 @@
"kind" : "remoteSourceControl",
"location" : "https://github.com/drmohundro/SWXMLHash.git",
"state" : {
- "revision" : "4d0f62f561458cbe1f732171e625f03195151b60",
- "version" : "7.0.1"
+ "revision" : "a853604c9e9a83ad9954c7e3d2a565273982471f",
+ "version" : "7.0.2"
}
},
{
diff --git a/Apple/NetworkExtension/PacketTunnelProvider.swift b/Apple/NetworkExtension/PacketTunnelProvider.swift
index 7073401..a07daa3 100644
--- a/Apple/NetworkExtension/PacketTunnelProvider.swift
+++ b/Apple/NetworkExtension/PacketTunnelProvider.swift
@@ -6,10 +6,16 @@ import os
class PacketTunnelProvider: NEPacketTunnelProvider {
private let logger = Logger.logger(for: PacketTunnelProvider.self)
- override func startTunnel(options: [String: NSObject]? = nil) async throws {
+ override init() {
do {
libburrow.spawnInProcess(socketPath: try Constants.socketURL.path)
+ } catch {
+ logger.error("Failed to spawn: \(error)")
+ }
+ }
+ override func startTunnel(options: [String: NSObject]? = nil) async throws {
+ do {
let client = try Client()
let command = BurrowRequest(id: 0, command: "ServerConfig")
diff --git a/Apple/Shared/Constants.swift b/Apple/Shared/Constants.swift
index cb56cb3..634c500 100644
--- a/Apple/Shared/Constants.swift
+++ b/Apple/Shared/Constants.swift
@@ -7,6 +7,7 @@ public enum Constants {
public static let bundleIdentifier = AppBundleIdentifier
public static let appGroupIdentifier = AppGroupIdentifier
+ public static let networkExtensionBundleIdentifier = NetworkExtensionBundleIdentifier
public static var groupContainerURL: URL {
get throws { try _groupContainerURL.get() }
diff --git a/Apple/Shared/Constants/Constants.h b/Apple/Shared/Constants/Constants.h
index 09806c5..5278b61 100644
--- a/Apple/Shared/Constants/Constants.h
+++ b/Apple/Shared/Constants/Constants.h
@@ -7,5 +7,6 @@ NS_ASSUME_NONNULL_BEGIN
static NSString * const AppBundleIdentifier = MACRO_STRING(APP_BUNDLE_IDENTIFIER);
static NSString * const AppGroupIdentifier = MACRO_STRING(APP_GROUP_IDENTIFIER);
+static NSString * const NetworkExtensionBundleIdentifier = MACRO_STRING(NETWORK_EXTENSION_BUNDLE_IDENTIFIER);
NS_ASSUME_NONNULL_END
diff --git a/Apple/Shared/Shared.xcconfig b/Apple/Shared/Shared.xcconfig
index 50718bd..f344e8b 100644
--- a/Apple/Shared/Shared.xcconfig
+++ b/Apple/Shared/Shared.xcconfig
@@ -2,4 +2,4 @@ PRODUCT_NAME = BurrowShared
MERGEABLE_LIBRARY = YES
SWIFT_INCLUDE_PATHS = $(PROJECT_DIR)/Shared/Constants
-GCC_PREPROCESSOR_DEFINITIONS = APP_BUNDLE_IDENTIFIER=$(APP_BUNDLE_IDENTIFIER) APP_GROUP_IDENTIFIER=$(APP_GROUP_IDENTIFIER)
+GCC_PREPROCESSOR_DEFINITIONS = APP_BUNDLE_IDENTIFIER=$(APP_BUNDLE_IDENTIFIER) APP_GROUP_IDENTIFIER=$(APP_GROUP_IDENTIFIER) NETWORK_EXTENSION_BUNDLE_IDENTIFIER=$(NETWORK_EXTENSION_BUNDLE_IDENTIFIER)