mirror of
https://github.com/samsonjs/vibetunnel.git
synced 2026-03-25 09:25:50 +00:00
warning fixes
This commit is contained in:
parent
fe2f30ed73
commit
e9a1ce0555
11 changed files with 92 additions and 81 deletions
|
|
@ -13,9 +13,9 @@ extension Notification.Name {
|
|||
// MARK: - Welcome
|
||||
|
||||
static let showWelcomeScreen = Notification.Name("showWelcomeScreen")
|
||||
|
||||
|
||||
// MARK: - Services
|
||||
|
||||
|
||||
static let notificationServiceConnectionChanged = Notification.Name("notificationServiceConnectionChanged")
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -60,9 +60,9 @@ final class EventSource: NSObject {
|
|||
// MARK: - Connection Management
|
||||
|
||||
func connect() {
|
||||
guard !isConnected else {
|
||||
guard !isConnected else {
|
||||
logger.warning("Already connected, ignoring connect request")
|
||||
return
|
||||
return
|
||||
}
|
||||
|
||||
var request = URLRequest(url: url)
|
||||
|
|
@ -84,7 +84,7 @@ final class EventSource: NSObject {
|
|||
|
||||
dataTask = urlSession?.dataTask(with: request)
|
||||
dataTask?.resume()
|
||||
|
||||
|
||||
logger.info("📡 EventSource dataTask started")
|
||||
}
|
||||
|
||||
|
|
@ -136,7 +136,8 @@ final class EventSource: NSObject {
|
|||
}
|
||||
|
||||
// Dispatch event
|
||||
logger.debug("🎯 Dispatching event - type: \(event.event ?? "default"), data: \(event.data ?? "none")")
|
||||
logger
|
||||
.debug("🎯 Dispatching event - type: \(event.event ?? "default"), data: \(event.data ?? "none")")
|
||||
DispatchQueue.main.async {
|
||||
self.onMessage?(event)
|
||||
}
|
||||
|
|
@ -197,7 +198,7 @@ extension EventSource: URLSessionDataDelegate {
|
|||
completionHandler: @escaping (URLSession.ResponseDisposition) -> Void
|
||||
) {
|
||||
logger.info("📥 URLSession didReceive response")
|
||||
|
||||
|
||||
guard let httpResponse = response as? HTTPURLResponse else {
|
||||
logger.error("Response is not HTTPURLResponse")
|
||||
completionHandler(.cancel)
|
||||
|
|
@ -224,19 +225,19 @@ extension EventSource: URLSessionDataDelegate {
|
|||
|
||||
func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive data: Data) {
|
||||
logger.debug("📨 EventSource received \(data.count) bytes of data")
|
||||
|
||||
|
||||
// Check if data might be compressed
|
||||
if data.count > 2 {
|
||||
let header = [UInt8](data.prefix(2))
|
||||
if header[0] == 0x1f && header[1] == 0x8b {
|
||||
if header[0] == 0x1F && header[1] == 0x8B {
|
||||
logger.error("❌ Received gzip compressed data! SSE should not be compressed.")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
guard let text = String(data: data, encoding: .utf8) else {
|
||||
|
||||
guard let text = String(data: data, encoding: .utf8) else {
|
||||
logger.error("Failed to decode data as UTF-8. First 20 bytes: \(data.prefix(20).hexString)")
|
||||
return
|
||||
return
|
||||
}
|
||||
|
||||
logger.debug("📨 EventSource received text: \(text)")
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ final class NotificationService: NSObject {
|
|||
private var isConnected = false
|
||||
private var recentlyNotifiedSessions = Set<String>()
|
||||
private var notificationCleanupTimer: Timer?
|
||||
|
||||
|
||||
/// Public property to check SSE connection status
|
||||
var isSSEConnected: Bool { isConnected }
|
||||
|
||||
|
|
@ -67,13 +67,13 @@ final class NotificationService: NSObject {
|
|||
/// Start monitoring server events
|
||||
func start() async {
|
||||
logger.info("🚀 NotificationService.start() called")
|
||||
|
||||
|
||||
// Check if notifications are enabled in config
|
||||
guard configManager.notificationsEnabled else {
|
||||
logger.info("📴 Notifications are disabled in config, skipping SSE connection")
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
guard serverManager.isRunning else {
|
||||
logger.warning("🔴 Server not running, cannot start notification service")
|
||||
return
|
||||
|
|
@ -87,18 +87,18 @@ final class NotificationService: NSObject {
|
|||
waitForUnixSocketAndConnect()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// Wait for Unix socket ready notification then connect
|
||||
private func waitForUnixSocketAndConnect() {
|
||||
logger.info("⏳ Waiting for Unix socket ready notification...")
|
||||
|
||||
|
||||
// Check if Unix socket is already connected
|
||||
if SharedUnixSocketManager.shared.isConnected {
|
||||
logger.info("✅ Unix socket already connected, connecting to SSE immediately")
|
||||
connect()
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
// Listen for Unix socket ready notification
|
||||
NotificationCenter.default.addObserver(
|
||||
forName: SharedUnixSocketManager.unixSocketReadyNotification,
|
||||
|
|
@ -108,7 +108,7 @@ final class NotificationService: NSObject {
|
|||
Task { @MainActor [weak self] in
|
||||
self?.logger.info("✅ Unix socket ready notification received, connecting to SSE")
|
||||
self?.connect()
|
||||
|
||||
|
||||
// Remove observer after first notification to prevent duplicate connections
|
||||
NotificationCenter.default.removeObserver(
|
||||
self as Any,
|
||||
|
|
@ -423,7 +423,6 @@ final class NotificationService: NSObject {
|
|||
// This prevents dual-path connection attempts
|
||||
}
|
||||
|
||||
|
||||
private func connect() {
|
||||
logger.info("🔌 NotificationService.connect() called - isConnected: \(self.isConnected)")
|
||||
guard !isConnected else {
|
||||
|
|
@ -434,13 +433,14 @@ final class NotificationService: NSObject {
|
|||
// When auth mode is "none", we can connect without a token.
|
||||
// In any other auth mode, a token is required for the local Mac app to connect.
|
||||
if serverManager.authMode != "none", serverManager.localAuthToken == nil {
|
||||
logger.error("No auth token available for notification service in auth mode '\(self.serverManager.authMode)'")
|
||||
logger
|
||||
.error("No auth token available for notification service in auth mode '\(self.serverManager.authMode)'")
|
||||
return
|
||||
}
|
||||
|
||||
let eventsURL = "http://localhost:\(self.serverManager.port)/api/events"
|
||||
logger.info("📡 Attempting to connect to SSE endpoint: \(eventsURL)")
|
||||
|
||||
|
||||
guard let url = URL(string: eventsURL) else {
|
||||
logger.error("Invalid events URL: \(eventsURL)")
|
||||
return
|
||||
|
|
@ -489,7 +489,10 @@ final class NotificationService: NSObject {
|
|||
|
||||
eventSource?.onMessage = { [weak self] event in
|
||||
Task { @MainActor in
|
||||
self?.logger.info("🎯 EventSource onMessage fired! Event type: \(event.event ?? "default"), Has data: \(event.data != nil)")
|
||||
self?.logger
|
||||
.info(
|
||||
"🎯 EventSource onMessage fired! Event type: \(event.event ?? "default"), Has data: \(event.data != nil)"
|
||||
)
|
||||
self?.handleEvent(event)
|
||||
}
|
||||
}
|
||||
|
|
@ -507,9 +510,9 @@ final class NotificationService: NSObject {
|
|||
}
|
||||
|
||||
private func handleEvent(_ event: Event) {
|
||||
guard let data = event.data else {
|
||||
guard let data = event.data else {
|
||||
logger.warning("Received event with no data")
|
||||
return
|
||||
return
|
||||
}
|
||||
|
||||
// Log event details for debugging
|
||||
|
|
@ -725,25 +728,25 @@ final class NotificationService: NSObject {
|
|||
|
||||
private func handleTestNotification(_ json: [String: Any]) {
|
||||
logger.info("🧪 Handling test notification from server")
|
||||
|
||||
|
||||
let title = json["title"] as? String ?? "VibeTunnel Test"
|
||||
let body = json["body"] as? String ?? "Server-side notifications are working correctly!"
|
||||
let message = json["message"] as? String
|
||||
|
||||
|
||||
let content = UNMutableNotificationContent()
|
||||
content.title = title
|
||||
content.body = body
|
||||
if let message = message {
|
||||
if let message {
|
||||
content.subtitle = message
|
||||
}
|
||||
content.sound = getNotificationSound()
|
||||
content.categoryIdentifier = "TEST"
|
||||
content.userInfo = ["type": "test-notification"]
|
||||
|
||||
|
||||
logger.info("📤 Delivering test notification: \(title) - \(body)")
|
||||
deliverNotification(content, identifier: "test-\(UUID().uuidString)")
|
||||
}
|
||||
|
||||
|
||||
private func handleClaudeTurn(_ json: [String: Any]) {
|
||||
guard let sessionId = json["sessionId"] as? String else {
|
||||
logger.error("Claude turn event missing sessionId")
|
||||
|
|
@ -803,13 +806,13 @@ final class NotificationService: NSObject {
|
|||
/// Send a test notification through the server to verify the full flow
|
||||
func sendServerTestNotification() async {
|
||||
logger.info("🧪 Sending test notification through server...")
|
||||
|
||||
|
||||
// Check if server is running
|
||||
guard serverManager.isRunning else {
|
||||
logger.error("❌ Cannot send test notification - server is not running")
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
// If not connected to SSE, try to connect first
|
||||
if !isConnected {
|
||||
logger.warning("⚠️ Not connected to SSE endpoint, attempting to connect...")
|
||||
|
|
@ -819,33 +822,36 @@ final class NotificationService: NSObject {
|
|||
// Give it a moment to connect
|
||||
try? await Task.sleep(nanoseconds: 500_000_000) // 0.5 seconds
|
||||
}
|
||||
|
||||
|
||||
// Log server info
|
||||
logger.info("Server info - Port: \(self.serverManager.port), Running: \(self.serverManager.isRunning), SSE Connected: \(self.isConnected)")
|
||||
|
||||
logger
|
||||
.info(
|
||||
"Server info - Port: \(self.serverManager.port), Running: \(self.serverManager.isRunning), SSE Connected: \(self.isConnected)"
|
||||
)
|
||||
|
||||
guard let url = serverManager.buildURL(endpoint: "/api/test-notification") else {
|
||||
logger.error("❌ Failed to build test notification URL")
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
logger.info("📤 Sending POST request to: \(url)")
|
||||
|
||||
|
||||
var request = URLRequest(url: url)
|
||||
request.httpMethod = "POST"
|
||||
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
|
||||
|
||||
|
||||
// Add auth token if available
|
||||
if let authToken = serverManager.localAuthToken {
|
||||
request.setValue("Bearer \(authToken)", forHTTPHeaderField: "Authorization")
|
||||
logger.debug("Added auth token to request")
|
||||
}
|
||||
|
||||
|
||||
do {
|
||||
let (data, response) = try await URLSession.shared.data(for: request)
|
||||
|
||||
|
||||
if let httpResponse = response as? HTTPURLResponse {
|
||||
logger.info("📥 Received response - Status: \(httpResponse.statusCode)")
|
||||
|
||||
|
||||
if httpResponse.statusCode == 200 {
|
||||
logger.info("✅ Server test notification sent successfully")
|
||||
if let responseData = String(data: data, encoding: .utf8) {
|
||||
|
|
@ -870,4 +876,3 @@ final class NotificationService: NSObject {
|
|||
// NotificationCenter observers are automatically removed on deinit in modern Swift
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -134,7 +134,7 @@ final class SessionMonitor {
|
|||
private func fetchSessions() async {
|
||||
do {
|
||||
// Snapshot previous sessions for exit notifications
|
||||
let _ = sessions
|
||||
_ = sessions
|
||||
|
||||
let sessionsArray = try await serverManager.performRequest(
|
||||
endpoint: APIEndpoints.sessions,
|
||||
|
|
@ -233,4 +233,4 @@ final class SessionMonitor {
|
|||
"Pre-caching Git data for \(uniqueDirectoriesToCheck.count) unique directories (from \(sessions.count) sessions)"
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -22,9 +22,9 @@ final class SharedUnixSocketManager {
|
|||
private init() {
|
||||
logger.info("🚀 SharedUnixSocketManager initialized")
|
||||
}
|
||||
|
||||
|
||||
// MARK: - Notifications
|
||||
|
||||
|
||||
static let unixSocketReadyNotification = Notification.Name("unixSocketReady")
|
||||
|
||||
// MARK: - Public Methods
|
||||
|
|
@ -45,7 +45,7 @@ final class SharedUnixSocketManager {
|
|||
self?.distributeMessage(data)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Set up state change handler to notify when socket is ready
|
||||
socket.onStateChange = { [weak self] state in
|
||||
Task { @MainActor [weak self] in
|
||||
|
|
@ -56,7 +56,7 @@ final class SharedUnixSocketManager {
|
|||
unixSocket = socket
|
||||
return socket
|
||||
}
|
||||
|
||||
|
||||
/// Handle socket state changes and notify when ready
|
||||
private func handleSocketStateChange(_ state: UnixSocketConnection.ConnectionState) {
|
||||
switch state {
|
||||
|
|
|
|||
|
|
@ -74,11 +74,13 @@ final class TailscaleServeStatusService {
|
|||
|
||||
let decoder = JSONDecoder()
|
||||
// Use custom date decoder to handle ISO8601 with fractional seconds
|
||||
let formatter = ISO8601DateFormatter()
|
||||
formatter.formatOptions = [.withInternetDateTime, .withFractionalSeconds]
|
||||
decoder.dateDecodingStrategy = .custom { decoder in
|
||||
let container = try decoder.singleValueContainer()
|
||||
let dateString = try container.decode(String.self)
|
||||
|
||||
// Create formatter inside the closure to avoid Sendable warning
|
||||
let formatter = ISO8601DateFormatter()
|
||||
formatter.formatOptions = [.withInternetDateTime, .withFractionalSeconds]
|
||||
if let date = formatter.date(from: dateString) {
|
||||
return date
|
||||
}
|
||||
|
|
@ -87,7 +89,10 @@ final class TailscaleServeStatusService {
|
|||
if let date = formatter.date(from: dateString) {
|
||||
return date
|
||||
}
|
||||
throw DecodingError.dataCorruptedError(in: container, debugDescription: "Cannot decode date string \(dateString)")
|
||||
throw DecodingError.dataCorruptedError(
|
||||
in: container,
|
||||
debugDescription: "Cannot decode date string \(dateString)"
|
||||
)
|
||||
}
|
||||
|
||||
let status = try decoder.decode(TailscaleServeStatus.self, from: data)
|
||||
|
|
@ -98,7 +103,6 @@ final class TailscaleServeStatusService {
|
|||
startTime = status.startTime
|
||||
|
||||
logger.debug("Tailscale Serve status - Running: \(status.isRunning), Error: \(status.lastError ?? "none")")
|
||||
|
||||
} catch {
|
||||
logger.error("Failed to fetch Tailscale Serve status: \(error.localizedDescription)")
|
||||
// On error, assume not running
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import SwiftUI
|
||||
import AppKit
|
||||
import os.log
|
||||
import SwiftUI
|
||||
|
||||
/// Authentication configuration section for remote access settings
|
||||
struct AuthenticationSection: View {
|
||||
|
|
@ -8,7 +8,7 @@ struct AuthenticationSection: View {
|
|||
@Binding var enableSSHKeys: Bool
|
||||
let logger: Logger
|
||||
let serverManager: ServerManager
|
||||
|
||||
|
||||
var body: some View {
|
||||
Section {
|
||||
VStack(alignment: .leading, spacing: 16) {
|
||||
|
|
@ -45,7 +45,7 @@ struct AuthenticationSection: View {
|
|||
.fixedSize(horizontal: false, vertical: true)
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
}
|
||||
|
||||
|
||||
// Additional info based on selected mode
|
||||
if authMode == .osAuth || authMode == .both {
|
||||
HStack(alignment: .center, spacing: 6) {
|
||||
|
|
@ -59,7 +59,7 @@ struct AuthenticationSection: View {
|
|||
Spacer()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if authMode == .sshKeys || authMode == .both {
|
||||
HStack(alignment: .center, spacing: 6) {
|
||||
Image(systemName: "key.fill")
|
||||
|
|
@ -105,7 +105,7 @@ struct AuthenticationSection: View {
|
|||
struct PreviewWrapper: View {
|
||||
@State var authMode = AuthenticationMode.osAuth
|
||||
@State var enableSSHKeys = false
|
||||
|
||||
|
||||
var body: some View {
|
||||
AuthenticationSection(
|
||||
authMode: $authMode,
|
||||
|
|
@ -117,6 +117,6 @@ struct AuthenticationSection: View {
|
|||
.padding()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return PreviewWrapper()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@ struct NotificationSettingsView: View {
|
|||
var body: some View {
|
||||
NavigationStack {
|
||||
@Bindable var bindableConfig = configManager
|
||||
|
||||
|
||||
Form {
|
||||
// Master toggle section
|
||||
Section {
|
||||
|
|
@ -37,7 +37,7 @@ struct NotificationSettingsView: View {
|
|||
.onChange(of: showNotifications) { _, newValue in
|
||||
// Update ConfigManager's notificationsEnabled to match
|
||||
configManager.notificationsEnabled = newValue
|
||||
|
||||
|
||||
// Ensure NotificationService starts/stops based on the toggle
|
||||
if newValue {
|
||||
Task {
|
||||
|
|
@ -63,7 +63,7 @@ struct NotificationSettingsView: View {
|
|||
Text("Display native macOS notifications for session and command events")
|
||||
.font(.caption)
|
||||
.foregroundStyle(.secondary)
|
||||
|
||||
|
||||
// SSE Connection Status Row
|
||||
HStack(spacing: 6) {
|
||||
Circle()
|
||||
|
|
@ -78,10 +78,11 @@ struct NotificationSettingsView: View {
|
|||
.fontWeight(.medium)
|
||||
Spacer()
|
||||
}
|
||||
.help(sseConnectionStatus
|
||||
? "Real-time notification stream is connected"
|
||||
: "Real-time notification stream is disconnected. Check if the server is running.")
|
||||
|
||||
.help(sseConnectionStatus
|
||||
? "Real-time notification stream is connected"
|
||||
: "Real-time notification stream is disconnected. Check if the server is running."
|
||||
)
|
||||
|
||||
// Show warning when disconnected
|
||||
if showNotifications && !sseConnectionStatus {
|
||||
HStack(spacing: 6) {
|
||||
|
|
@ -198,7 +199,7 @@ struct NotificationSettingsView: View {
|
|||
.scaleEffect(0.7)
|
||||
.frame(width: 16, height: 16)
|
||||
}
|
||||
|
||||
|
||||
Spacer()
|
||||
}
|
||||
|
||||
|
|
@ -207,7 +208,7 @@ struct NotificationSettingsView: View {
|
|||
notificationService.openNotificationSettings()
|
||||
}
|
||||
.buttonStyle(.link)
|
||||
|
||||
|
||||
Spacer()
|
||||
}
|
||||
}
|
||||
|
|
@ -223,7 +224,7 @@ struct NotificationSettingsView: View {
|
|||
.onAppear {
|
||||
// Sync the AppStorage value with ConfigManager on first load
|
||||
showNotifications = configManager.notificationsEnabled
|
||||
|
||||
|
||||
// Update initial connection status
|
||||
sseConnectionStatus = notificationService.isSSEConnected
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ struct PermissionsSection: View {
|
|||
let hasAppleScriptPermission: Bool
|
||||
let hasAccessibilityPermission: Bool
|
||||
let permissionManager: SystemPermissionManager
|
||||
|
||||
|
||||
var body: some View {
|
||||
Section {
|
||||
// Automation permission
|
||||
|
|
@ -17,9 +17,9 @@ struct PermissionsSection: View {
|
|||
.font(.caption)
|
||||
.foregroundStyle(.secondary)
|
||||
}
|
||||
|
||||
|
||||
Spacer()
|
||||
|
||||
|
||||
if hasAppleScriptPermission {
|
||||
HStack {
|
||||
Image(systemName: "checkmark.circle.fill")
|
||||
|
|
@ -47,7 +47,7 @@ struct PermissionsSection: View {
|
|||
.controlSize(.small)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Accessibility permission
|
||||
HStack {
|
||||
VStack(alignment: .leading, spacing: 4) {
|
||||
|
|
@ -57,9 +57,9 @@ struct PermissionsSection: View {
|
|||
.font(.caption)
|
||||
.foregroundStyle(.secondary)
|
||||
}
|
||||
|
||||
|
||||
Spacer()
|
||||
|
||||
|
||||
if hasAccessibilityPermission {
|
||||
HStack {
|
||||
Image(systemName: "checkmark.circle.fill")
|
||||
|
|
@ -115,7 +115,7 @@ struct PermissionsSection: View {
|
|||
struct PreviewWrapper: View {
|
||||
@State var hasAppleScript = true
|
||||
@State var hasAccessibility = false
|
||||
|
||||
|
||||
var body: some View {
|
||||
PermissionsSection(
|
||||
hasAppleScriptPermission: hasAppleScript,
|
||||
|
|
@ -126,6 +126,6 @@ struct PermissionsSection: View {
|
|||
.padding()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return PreviewWrapper()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -94,7 +94,8 @@ struct RemoteAccessSettingsView: View {
|
|||
onAppearSetup()
|
||||
updateLocalIPAddress()
|
||||
// Initialize authentication mode from stored value
|
||||
let storedMode = UserDefaults.standard.string(forKey: AppConstants.UserDefaultsKeys.authenticationMode) ?? "os"
|
||||
let storedMode = UserDefaults.standard
|
||||
.string(forKey: AppConstants.UserDefaultsKeys.authenticationMode) ?? "os"
|
||||
authMode = AuthenticationMode(rawValue: storedMode) ?? .osAuth
|
||||
// Start monitoring Tailscale Serve status
|
||||
tailscaleServeStatus.startMonitoring()
|
||||
|
|
@ -628,7 +629,6 @@ private struct ErrorView: View {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
// MARK: - Previews
|
||||
|
||||
#Preview("Remote Access Settings") {
|
||||
|
|
|
|||
|
|
@ -299,7 +299,7 @@ final class AppDelegate: NSObject, NSApplicationDelegate, @preconcurrency UNUser
|
|||
statusBarController?.updateStatusItemDisplay()
|
||||
|
||||
// Session monitoring starts automatically
|
||||
|
||||
|
||||
// NotificationService is started by ServerManager when the server is ready
|
||||
} else {
|
||||
logger.error("HTTP server failed to start")
|
||||
|
|
|
|||
Loading…
Reference in a new issue