From 0fe630878dd9ea236c4daa09575cf5563c67e79c 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 | 168 +---- 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, 1453 insertions(+), 316 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 d6097183..22ef0359 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 1d63205d..4e42ddcf 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 f42b52ff..6085d850 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 00000000..911b4b1a --- /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 00000000..ddd06647 --- /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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 literal 0 HcmV?d00001 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 00000000..092ec699 --- /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 00000000..e7fe15a9 --- /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 00000000..9520f895 --- /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 00000000..782dd122 --- /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 00000000..64946dac --- /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 e8aed86a..21ebf849 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 00000000..b78b1e1a --- /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 00000000..53ab5edb --- /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 00000000..8933f308 --- /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 eab8da26..00000000 --- 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 00000000..07db51da --- /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 00000000..08002de6 --- /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 00000000..290254c9 --- /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 00000000..f7df674d --- /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 00000000..d441d241 --- /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 00000000..499288a2 --- /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 c08cdd1d..00000000 --- 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 55421704..8db366f2 100644 --- a/Apple/App/Tunnel.swift +++ b/Apple/App/Tunnel.swift @@ -1,146 +1,50 @@ -import BurrowShared -import NetworkExtension import SwiftUI -@Observable -class Tunnel { - private(set) var status: Status = .unknown - private var error: NEVPNError? - - private let logger = Logger.logger(for: Tunnel.self) - private let bundleIdentifier: String - private let configure: (NETunnelProviderManager, NETunnelProviderProtocol) -> Void - private var tasks: [Task] = [] - - // Each manager corresponds to one entry in the Settings app. - // Our goal is to maintain a single manager, so we create one if none exist and delete extra if there are any. - private var managers: [NEVPNManager]? { - didSet { status = currentStatus } - } - - private var currentStatus: Status { - guard let managers = managers else { - guard let error = error else { - return .unknown - } - - switch error.code { - case .configurationReadWriteFailed: - return .configurationReadWriteFailed - default: - return .unknown - } - } - - guard let manager = managers.first else { - return .permissionRequired - } +protocol Tunnel { + var status: TunnelStatus { get } - 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 + func start() + func stop() + func enable() +} - 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] +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 +} - Task { await update() } - } +struct TunnelKey: EnvironmentKey { + static let defaultValue: any Tunnel = NetworkExtensionTunnel() +} - 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)") - } +extension EnvironmentValues { + var tunnel: any Tunnel { + get { self[TunnelKey.self] } + set { self[TunnelKey.self] = newValue } } +} - 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() - } - } +#if DEBUG +@Observable +class PreviewTunnel: Tunnel { + var status: TunnelStatus = .permissionRequired - func start() throws { - guard let manager = managers?.first else { return } - try manager.connection.startVPNTunnel() + func start() { + status = .connected(.now) } - 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 00000000..df8d7e60 --- /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 00000000..3593516a --- /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 dd91603d..00000000 --- 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 6127e1a7..8717a30f 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 75228402..93783729 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 70734013..a07daa3f 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 cb56cb38..634c500d 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 09806c54..5278b613 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 50718bd2..f344e8b8 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)