Skip to content

Commit ce2d4a4

Browse files
feat: send push notifications on VPN failures (#266)
1 parent 69e4d72 commit ce2d4a4

3 files changed

Lines changed: 55 additions & 3 deletions

File tree

‎Coder-Desktop/Coder-Desktop/Coder_DesktopApp.swift‎

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ struct DesktopApp: App {
4444
@MainActor
4545
class AppDelegate: NSObject, NSApplicationDelegate {
4646
private var logger = Logger(subsystem: Bundle.main.bundleIdentifier!, category: "app-delegate")
47-
private var menuBar: MenuBarController?
47+
var menuBar: MenuBarController?
4848
let vpn: CoderVPNService
4949
let state: AppState
5050
let fileSyncDaemon: MutagenDaemon
@@ -81,6 +81,19 @@ class AppDelegate: NSObject, NSApplicationDelegate {
8181
super.init()
8282
// `delegate` is weak
8383
UNUserNotificationCenter.current().delegate = self
84+
vpn.onFailure = { [logger] tunnelError in
85+
Task {
86+
do {
87+
try await sendNotification(
88+
title: "Coder Connect has failed!",
89+
body: tunnelError.description,
90+
category: .vpnFailure
91+
)
92+
} catch let notifError {
93+
logger.error("Failed to send notification (\(tunnelError.description)): \(notifError)")
94+
}
95+
}
96+
}
8497
}
8598

8699
func applicationDidFinishLaunching(_: Notification) {

‎Coder-Desktop/Coder-Desktop/Notifications.swift‎

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,24 @@ extension AppDelegate: UNUserNotificationCenterDelegate {
1717
) async -> UNNotificationPresentationOptions {
1818
[.banner]
1919
}
20+
21+
nonisolated func userNotificationCenter(
22+
_: UNUserNotificationCenter,
23+
didReceive response: UNNotificationResponse
24+
) async {
25+
let category = response.notification.request.content.categoryIdentifier
26+
let action = response.actionIdentifier
27+
switch (category, action) {
28+
case (NotificationCategory.vpnFailure.rawValue, UNNotificationDefaultActionIdentifier):
29+
await showMenuBarWindow()
30+
default:
31+
break
32+
}
33+
}
34+
35+
private func showMenuBarWindow() {
36+
menuBar?.menuBarExtra.toggleVisibility()
37+
}
2038
}
2139

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

3553
enum NotificationCategory: String, CaseIterable {
54+
case vpnFailure = "VPN_FAILURE"
3655
case uriFailure = "URI_FAILURE"
3756
}

‎Coder-Desktop/Coder-Desktop/VPN/VPNService.swift‎

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -61,11 +61,30 @@ final class CoderVPNService: NSObject, VPNService {
6161
if tunnelState == .connecting {
6262
progress = .init(stage: .initial, downloadProgress: nil)
6363
}
64+
if case let .failed(tunnelError) = tunnelState, tunnelState != oldValue,
65+
tunnelError != .networkExtensionError(.unconfigured)
66+
{
67+
onFailure?(tunnelError)
68+
}
69+
}
70+
}
71+
72+
@Published var sysExtnState: SystemExtensionState = .uninstalled {
73+
didSet {
74+
if case .failed = sysExtnState, sysExtnState != oldValue {
75+
onFailure?(.systemExtensionError(sysExtnState))
76+
}
77+
}
78+
}
79+
80+
@Published var neState: NetworkExtensionState = .unconfigured {
81+
didSet {
82+
if case .failed = neState, neState != oldValue {
83+
onFailure?(.networkExtensionError(neState))
84+
}
6485
}
6586
}
6687

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

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

0 commit comments

Comments
 (0)