From ab2d9e9b5cf70601488c33937fb68d91adf42097 Mon Sep 17 00:00:00 2001 From: Ethan Dickson Date: Mon, 5 May 2025 13:36:19 +1000 Subject: [PATCH 1/2] feat: send push notifications for invalid `coder` scheme URIs --- .../Coder-Desktop/Coder_DesktopApp.swift | 18 ++++++++++-- .../Coder-Desktop/Notifications.swift | 28 +++++++++++++++++++ Coder-Desktop/project.yml | 1 + 3 files changed, 44 insertions(+), 3 deletions(-) create mode 100644 Coder-Desktop/Coder-Desktop/Notifications.swift diff --git a/Coder-Desktop/Coder-Desktop/Coder_DesktopApp.swift b/Coder-Desktop/Coder-Desktop/Coder_DesktopApp.swift index 4d78735..659ec4b 100644 --- a/Coder-Desktop/Coder-Desktop/Coder_DesktopApp.swift +++ b/Coder-Desktop/Coder-Desktop/Coder_DesktopApp.swift @@ -1,8 +1,10 @@ import FluidMenuBarExtra import NetworkExtension +import os import SDWebImageSVGCoder import SDWebImageSwiftUI import SwiftUI +import UserNotifications import VPNLib @main @@ -36,13 +38,16 @@ struct DesktopApp: App { @MainActor class AppDelegate: NSObject, NSApplicationDelegate { + private var logger = Logger(subsystem: Bundle.main.bundleIdentifier!, category: "app-delegate") private var menuBar: MenuBarController? let vpn: CoderVPNService let state: AppState let fileSyncDaemon: MutagenDaemon let urlHandler: URLHandler + let notifDelegate: NotifDelegate override init() { + notifDelegate = NotifDelegate() vpn = CoderVPNService() let state = AppState(onChange: vpn.configureTunnelProviderProtocol) vpn.onStart = { @@ -67,6 +72,8 @@ class AppDelegate: NSObject, NSApplicationDelegate { } self.fileSyncDaemon = fileSyncDaemon urlHandler = URLHandler(state: state, vpn: vpn) + // `delegate` is weak + UNUserNotificationCenter.current().delegate = notifDelegate } func applicationDidFinishLaunching(_: Notification) { @@ -141,9 +148,14 @@ class AppDelegate: NSObject, NSApplicationDelegate { // We only accept one at time, for now return } - do { try urlHandler.handle(url) } catch { - // TODO: Push notification - print(error.description) + do { try urlHandler.handle(url) } catch let handleError { + Task { + do { + try await sendNotification(title: "Failed to open link", body: handleError.description) + } catch let notifError { + logger.error("Failed to send notification (\(handleError.description)): \(notifError)") + } + } } } diff --git a/Coder-Desktop/Coder-Desktop/Notifications.swift b/Coder-Desktop/Coder-Desktop/Notifications.swift new file mode 100644 index 0000000..44a2afb --- /dev/null +++ b/Coder-Desktop/Coder-Desktop/Notifications.swift @@ -0,0 +1,28 @@ +import UserNotifications + +class NotifDelegate: NSObject, UNUserNotificationCenterDelegate { + override init() { + super.init() + } + + // This function is required for notifications to appear as banners whilst the app is running. + // We're effectively forwarding the notification back to the OS + nonisolated func userNotificationCenter( + _: UNUserNotificationCenter, + willPresent _: UNNotification + ) async -> UNNotificationPresentationOptions { + [.banner] + } +} + +func sendNotification(title: String, body: String) async throws { + let nc = UNUserNotificationCenter.current() + let granted = try await nc.requestAuthorization(options: [.alert, .badge]) + guard granted else { + return + } + let content = UNMutableNotificationContent() + content.title = title + content.body = body + try await nc.add(.init(identifier: UUID().uuidString, content: content, trigger: nil)) +} diff --git a/Coder-Desktop/project.yml b/Coder-Desktop/project.yml index 9ec3ba4..f2c96fa 100644 --- a/Coder-Desktop/project.yml +++ b/Coder-Desktop/project.yml @@ -147,6 +147,7 @@ targets: com.apple.developer.system-extension.install: true com.apple.security.application-groups: - $(TeamIdentifierPrefix)com.coder.Coder-Desktop + aps-environment: development settings: base: ASSETCATALOG_COMPILER_APPICON_NAME: AppIcon # Sets the app icon to "AppIcon". From db9cf553a245ba3ed677f0770e1c11f183f4913f Mon Sep 17 00:00:00 2001 From: Ethan Dickson Date: Tue, 6 May 2025 14:22:49 +1000 Subject: [PATCH 2/2] wording --- Coder-Desktop/Coder-Desktop/Coder_DesktopApp.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Coder-Desktop/Coder-Desktop/Coder_DesktopApp.swift b/Coder-Desktop/Coder-Desktop/Coder_DesktopApp.swift index 659ec4b..e2fe3ab 100644 --- a/Coder-Desktop/Coder-Desktop/Coder_DesktopApp.swift +++ b/Coder-Desktop/Coder-Desktop/Coder_DesktopApp.swift @@ -151,7 +151,7 @@ class AppDelegate: NSObject, NSApplicationDelegate { do { try urlHandler.handle(url) } catch let handleError { Task { do { - try await sendNotification(title: "Failed to open link", body: handleError.description) + try await sendNotification(title: "Failed to handle link", body: handleError.description) } catch let notifError { logger.error("Failed to send notification (\(handleError.description)): \(notifError)") }