Skip to content

Commit 3cf808f

Browse files
laurentftechclaude
andcommitted
fix: Prevent app termination when closing About window and support per-topic fetch_missed
- Fix About window causing menu bar icon to disappear by retaining window reference - Add fetch_missed support at topic level (previously only server level) - Topics are now grouped by fetch_missed setting, creating separate connections as needed Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1 parent b2623bf commit 3cf808f

File tree

3 files changed

+59
-18
lines changed

3 files changed

+59
-18
lines changed

Sources/Config.swift

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ struct TopicConfig: Codable {
1616
let silent: Bool?
1717
let clickUrl: ClickUrlConfig? // Control click behavior: true/false/custom URL
1818
let actions: [NotificationAction]?
19+
let fetchMissed: Bool?
1920

2021
enum CodingKeys: String, CodingKey {
2122
case name
@@ -25,6 +26,12 @@ struct TopicConfig: Codable {
2526
case silent
2627
case clickUrl = "click_url"
2728
case actions
29+
case fetchMissed = "fetch_missed"
30+
}
31+
32+
/// Whether to fetch missed messages for this topic (default: false)
33+
var shouldFetchMissed: Bool {
34+
fetchMissed ?? false
2835
}
2936
}
3037

@@ -219,7 +226,7 @@ final class ConfigManager: @unchecked Sendable {
219226
/// Known keys at each level of the config
220227
private static let knownRootKeys: Set<String> = ["servers"]
221228
private static let knownServerKeys: Set<String> = ["url", "token", "topics", "allowed_schemes", "allowed_domains", "fetch_missed"]
222-
private static let knownTopicKeys: Set<String> = ["name", "icon_path", "icon_symbol", "auto_run_script", "silent", "click_url", "actions"]
229+
private static let knownTopicKeys: Set<String> = ["name", "icon_path", "icon_symbol", "auto_run_script", "silent", "click_url", "actions", "fetch_missed"]
223230
private static let knownActionKeys: Set<String> = ["title", "type", "path", "url"]
224231

225232
/// Checks YAML for unknown keys that would be silently ignored

Sources/StatusBarController.swift

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ class StatusBarController: NSObject {
77
private var statusMenuItem: NSMenuItem?
88
private var serversSubmenu: NSMenu?
99
private var errorMenuItem: NSMenuItem?
10+
private var aboutWindow: NSWindow?
1011
var onReloadConfig: (() -> Void)?
1112

1213
// Connection tracking
@@ -154,6 +155,13 @@ class StatusBarController: NSObject {
154155
}
155156

156157
@objc func showAbout() {
158+
// Reuse existing window if already open
159+
if let existingWindow = aboutWindow, existingWindow.isVisible {
160+
existingWindow.makeKeyAndOrderFront(nil)
161+
NSApp.activate(ignoringOtherApps: true)
162+
return
163+
}
164+
157165
let window = NSWindow(
158166
contentRect: NSRect(x: 0, y: 0, width: 340, height: 200),
159167
styleMask: [.titled, .closable],
@@ -162,6 +170,7 @@ class StatusBarController: NSObject {
162170
)
163171
window.title = "About ntfy-macos"
164172
window.center()
173+
window.isReleasedWhenClosed = false // Keep window object alive after closing
165174

166175
let contentView = NSView(frame: window.contentView!.bounds)
167176

@@ -227,6 +236,7 @@ class StatusBarController: NSObject {
227236
contentView.addSubview(licenseLabel)
228237

229238
window.contentView = contentView
239+
aboutWindow = window // Store reference to prevent deallocation
230240
window.makeKeyAndOrderFront(nil)
231241
NSApp.activate(ignoringOtherApps: true)
232242
}

Sources/main.swift

Lines changed: 41 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -137,26 +137,50 @@ final class NtfyMacOS: NtfyClientDelegate, @unchecked Sendable {
137137
StatusBarController.shared.initializeServers(servers: serverInfos)
138138
}
139139

140-
// Create a client for each server
140+
// Create clients for each server, grouping topics by fetch_missed setting
141141
for serverConfig in config.servers {
142-
let topicNames = serverConfig.topics.map { $0.name }
143-
guard !topicNames.isEmpty else { continue }
144-
145-
Log.info("Creating client for \(serverConfig.url)...")
142+
guard !serverConfig.topics.isEmpty else { continue }
146143

147144
let authToken = ConfigManager.shared.getAuthToken(forServer: serverConfig.url)
148-
let client = NtfyClient(
149-
serverURL: serverConfig.url,
150-
topics: topicNames,
151-
authToken: authToken,
152-
fetchMissed: serverConfig.shouldFetchMissed
153-
)
154-
client.delegate = self
155-
self.clients.append(client)
156-
self.clientToServer[ObjectIdentifier(client)] = serverConfig.url
157-
158-
Log.info("Connecting to \(serverConfig.url)...")
159-
client.connect()
145+
146+
// Group topics by their fetch_missed setting (topic-level overrides server-level)
147+
let topicsWithFetchMissed = serverConfig.topics.filter { topic in
148+
topic.fetchMissed ?? serverConfig.shouldFetchMissed
149+
}.map { $0.name }
150+
151+
let topicsWithoutFetchMissed = serverConfig.topics.filter { topic in
152+
!(topic.fetchMissed ?? serverConfig.shouldFetchMissed)
153+
}.map { $0.name }
154+
155+
// Create client for topics that need fetch_missed
156+
if !topicsWithFetchMissed.isEmpty {
157+
Log.info("Creating client for \(serverConfig.url) (fetch_missed: true, topics: \(topicsWithFetchMissed.joined(separator: ", ")))...")
158+
let client = NtfyClient(
159+
serverURL: serverConfig.url,
160+
topics: topicsWithFetchMissed,
161+
authToken: authToken,
162+
fetchMissed: true
163+
)
164+
client.delegate = self
165+
self.clients.append(client)
166+
self.clientToServer[ObjectIdentifier(client)] = serverConfig.url
167+
client.connect()
168+
}
169+
170+
// Create client for topics that don't need fetch_missed
171+
if !topicsWithoutFetchMissed.isEmpty {
172+
Log.info("Creating client for \(serverConfig.url) (fetch_missed: false, topics: \(topicsWithoutFetchMissed.joined(separator: ", ")))...")
173+
let client = NtfyClient(
174+
serverURL: serverConfig.url,
175+
topics: topicsWithoutFetchMissed,
176+
authToken: authToken,
177+
fetchMissed: false
178+
)
179+
client.delegate = self
180+
self.clients.append(client)
181+
self.clientToServer[ObjectIdentifier(client)] = serverConfig.url
182+
client.connect()
183+
}
160184
}
161185
}
162186

0 commit comments

Comments
 (0)