From 453dd2d1169c8fb52e6a0e73aad7bbb2b9031df9 Mon Sep 17 00:00:00 2001 From: Conrad Kramer Date: Sat, 24 Feb 2024 09:49:07 -0800 Subject: [PATCH] Introduce initial UI for connecting to networks --- .swiftlint.yml | 5 +- Apple/App/App.xcconfig | 5 + Apple/App/AppDelegate.swift | 3 +- .../HackClub.colorset/Contents.json | 20 + .../HackClub.imageset/Contents.json | 12 + .../flag-standalone-wtransparent.pdf | Bin 0 -> 3501 bytes .../WireGuard.colorset/Contents.json | 20 + .../WireGuard.imageset/Contents.json | 15 + .../WireGuard.imageset/WireGuard.svg | 6 + .../WireGuardTitle.imageset/Contents.json | 21 + .../WireGuardTitle.svg | 3 + Apple/App/BurrowApp.swift | 16 +- Apple/App/BurrowView.swift | 26 + Apple/App/FloatingButtonStyle.swift | 50 ++ Apple/App/MainMenu.xib | 679 ++++++++++++++++++ Apple/App/Menu/MenuView.swift | 60 -- Apple/App/MenuItemToggleView.swift | 64 ++ Apple/App/NetworkExtensionTunnel.swift | 167 +++++ Apple/App/NetworkView.swift | 88 +++ Apple/App/Networks/HackClub.swift | 23 + Apple/App/Networks/Network.swift | 10 + Apple/App/Networks/WireGuard.swift | 30 + Apple/App/Status.swift | 42 -- Apple/App/Tunnel.swift | 178 ++--- Apple/App/TunnelButton.swift | 61 ++ Apple/App/TunnelStatusView.swift | 37 + Apple/App/TunnelView.swift | 34 - Apple/Burrow.xcodeproj/project.pbxproj | 76 +- .../xcshareddata/swiftpm/Package.resolved | 16 +- .../PacketTunnelProvider.swift | 8 +- Apple/Shared/Constants.swift | 1 + Apple/Shared/Constants/Constants.h | 1 + Apple/Shared/Shared.xcconfig | 2 +- 33 files changed, 1458 insertions(+), 321 deletions(-) create mode 100644 Apple/App/Assets.xcassets/HackClub.colorset/Contents.json create mode 100644 Apple/App/Assets.xcassets/HackClub.imageset/Contents.json create mode 100644 Apple/App/Assets.xcassets/HackClub.imageset/flag-standalone-wtransparent.pdf create mode 100644 Apple/App/Assets.xcassets/WireGuard.colorset/Contents.json create mode 100644 Apple/App/Assets.xcassets/WireGuard.imageset/Contents.json create mode 100644 Apple/App/Assets.xcassets/WireGuard.imageset/WireGuard.svg create mode 100644 Apple/App/Assets.xcassets/WireGuardTitle.imageset/Contents.json create mode 100644 Apple/App/Assets.xcassets/WireGuardTitle.imageset/WireGuardTitle.svg create mode 100644 Apple/App/BurrowView.swift create mode 100644 Apple/App/FloatingButtonStyle.swift create mode 100644 Apple/App/MainMenu.xib delete mode 100644 Apple/App/Menu/MenuView.swift create mode 100644 Apple/App/MenuItemToggleView.swift create mode 100644 Apple/App/NetworkExtensionTunnel.swift create mode 100644 Apple/App/NetworkView.swift create mode 100644 Apple/App/Networks/HackClub.swift create mode 100644 Apple/App/Networks/Network.swift create mode 100644 Apple/App/Networks/WireGuard.swift delete mode 100644 Apple/App/Status.swift create mode 100644 Apple/App/TunnelButton.swift create mode 100644 Apple/App/TunnelStatusView.swift delete mode 100644 Apple/App/TunnelView.swift 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 0000000000000000000000000000000000000000..1506fe9390e19160cb5f8732ffe6ba2c488975ce GIT binary patch literal 3501 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 + + + + + \ 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 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Default + + + + + + + Left to Right + + + + + + + Right to Left + + + + + + + + + + + Default + + + + + + + Left to Right + + + + + + + Right to Left + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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)