vibetunnel/mac/VibeTunnel/Core/Services/NotificationControlHandler.swift
Peter Steinberger dfe0cfda25 Fix test failures and resolve all linting warnings
- Fix notification preference tests to match default enabled: false
- Fix PtyManager initialization in integration tests
- Fix path splitting tests for macOS URL behavior
- Add hour formatting to duration display (1h 23m 45s format)
- Fix non-optional URL nil comparison warning
- Fix force unwrapping warning in EventSource.swift
- Apply SwiftFormat formatting fixes
- Update test expectations to match actual behavior
2025-07-27 17:44:50 +02:00

108 lines
3.5 KiB
Swift

import Foundation
import OSLog
import UserNotifications
/// Handles notification control messages via the unified control socket
@MainActor
final class NotificationControlHandler {
private let logger = Logger(subsystem: "sh.vibetunnel.vibetunnel", category: "NotificationControl")
// MARK: - Singleton
static let shared = NotificationControlHandler()
// MARK: - Properties
private let notificationService = NotificationService.shared
// MARK: - Initialization
private init() {
// Register handler with the shared socket manager
SharedUnixSocketManager.shared.registerControlHandler(for: .notification) { [weak self] data in
_ = await self?.handleMessage(data)
return nil // No response needed for notifications
}
logger.info("NotificationControlHandler initialized")
}
// MARK: - Message Handling
private func handleMessage(_ data: Data) async -> Data? {
do {
// First decode just to get the action
if let json = try JSONSerialization.jsonObject(with: data) as? [String: Any],
let action = json["action"] as? String
{
switch action {
case "show":
return await handleShowNotification(json)
default:
logger.warning("Unknown notification action: \(action)")
}
}
} catch {
logger.error("Failed to decode notification message: \(error)")
}
return nil
}
private func handleShowNotification(_ json: [String: Any]) async -> Data? {
guard let payload = json["payload"] as? [String: Any],
let title = payload["title"] as? String,
let body = payload["body"] as? String
else {
logger.error("Notification message missing required fields")
return nil
}
// Try to parse as ServerEvent-compatible structure
let typeString = payload["type"] as? String
let sessionId = payload["sessionId"] as? String
let sessionName = payload["sessionName"] as? String
let exitCode = payload["exitCode"] as? Int
let duration = payload["duration"] as? Int
let command = payload["command"] as? String
logger.info("Received notification: \(title) - \(body) (type: \(typeString ?? "unknown"))")
// Map type string to ServerEventType and create ServerEvent
if let typeString,
let eventType = ServerEventType(rawValue: typeString)
{
let serverEvent = ServerEvent(
type: eventType,
sessionId: sessionId,
sessionName: sessionName ?? title,
command: command,
exitCode: exitCode,
duration: duration,
message: body
)
// Use the consolidated notification method
await notificationService.sendNotification(for: serverEvent)
} else {
// Unknown event type - log and ignore
logger.warning("Unknown event type '\(typeString ?? "nil")' - ignoring notification request")
}
return nil
}
}
// MARK: - Supporting Types
/// Notification payload that can be converted to ServerEvent
private struct NotificationPayload: Codable {
let title: String
let body: String
let type: String?
let sessionId: String?
let sessionName: String?
let command: String?
let exitCode: Int?
let duration: Int?
}