Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 14 additions & 1 deletion Coder-Desktop/Coder-Desktop/Coder_DesktopApp.swift
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ struct DesktopApp: App {
@MainActor
class AppDelegate: NSObject, NSApplicationDelegate {
private var logger = Logger(subsystem: Bundle.main.bundleIdentifier!, category: "app-delegate")
private var menuBar: MenuBarController?
var menuBar: MenuBarController?
let vpn: CoderVPNService
let state: AppState
let fileSyncDaemon: MutagenDaemon
Expand Down Expand Up @@ -81,6 +81,19 @@ class AppDelegate: NSObject, NSApplicationDelegate {
super.init()
// `delegate` is weak
UNUserNotificationCenter.current().delegate = self
vpn.onFailure = { [logger] tunnelError in
Task {
do {
try await sendNotification(
title: "Coder Connect has failed!",
body: tunnelError.description,
category: .vpnFailure
)
} catch let notifError {
logger.error("Failed to send notification (\(tunnelError.description)): \(notifError)")
}
}
}
}

func applicationDidFinishLaunching(_: Notification) {
Expand Down
19 changes: 19 additions & 0 deletions Coder-Desktop/Coder-Desktop/Notifications.swift
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,24 @@ extension AppDelegate: UNUserNotificationCenterDelegate {
) async -> UNNotificationPresentationOptions {
[.banner]
}

nonisolated func userNotificationCenter(
_: UNUserNotificationCenter,
didReceive response: UNNotificationResponse
) async {
let category = response.notification.request.content.categoryIdentifier
let action = response.actionIdentifier
switch (category, action) {
case (NotificationCategory.vpnFailure.rawValue, UNNotificationDefaultActionIdentifier):
await showMenuBarWindow()
default:
break
}
}

private func showMenuBarWindow() {
menuBar?.menuBarExtra.toggleVisibility()
Comment thread
ethanndickson marked this conversation as resolved.
}
}

func sendNotification(title: String, body: String, category: NotificationCategory) async throws {
Expand All @@ -33,5 +51,6 @@ func sendNotification(title: String, body: String, category: NotificationCategor
}

enum NotificationCategory: String, CaseIterable {
case vpnFailure = "VPN_FAILURE"
case uriFailure = "URI_FAILURE"
}
24 changes: 22 additions & 2 deletions Coder-Desktop/Coder-Desktop/VPN/VPNService.swift
Original file line number Diff line number Diff line change
Expand Up @@ -61,11 +61,30 @@ final class CoderVPNService: NSObject, VPNService {
if tunnelState == .connecting {
progress = .init(stage: .initial, downloadProgress: nil)
}
if case let .failed(tunnelError) = tunnelState, tunnelState != oldValue,
tunnelError != .networkExtensionError(.unconfigured)
{
onFailure?(tunnelError)
}
}
}

@Published var sysExtnState: SystemExtensionState = .uninstalled {
didSet {
if case .failed = sysExtnState, sysExtnState != oldValue {
onFailure?(.systemExtensionError(sysExtnState))
}
}
}

@Published var neState: NetworkExtensionState = .unconfigured {
didSet {
if case .failed = neState, neState != oldValue {
onFailure?(.networkExtensionError(neState))
}
}
}

@Published var sysExtnState: SystemExtensionState = .uninstalled
@Published var neState: NetworkExtensionState = .unconfigured
var state: VPNServiceState {
guard sysExtnState == .installed else {
return .failed(.systemExtensionError(sysExtnState))
Expand All @@ -87,6 +106,7 @@ final class CoderVPNService: NSObject, VPNService {
// Whether the VPN should start as soon as possible
var startWhenReady: Bool = false
var onStart: (() -> Void)?
var onFailure: ((VPNServiceError) -> Void)?

// systemExtnDelegate holds a reference to the SystemExtensionDelegate so that it doesn't get
// garbage collected while the OSSystemExtensionRequest is in flight, since the OS framework
Expand Down