mirror of
https://github.com/samsonjs/vibetunnel.git
synced 2026-04-11 12:15:53 +00:00
119 lines
3.5 KiB
Swift
119 lines
3.5 KiB
Swift
import Foundation
|
|
import Observation
|
|
|
|
/// Server session information returned by the API
|
|
struct ServerSessionInfo: Codable {
|
|
let id: String
|
|
let command: String
|
|
let workingDir: String
|
|
let status: String
|
|
let exitCode: Int?
|
|
let startedAt: String
|
|
let lastModified: String
|
|
let pid: Int
|
|
|
|
var isRunning: Bool {
|
|
status == "running"
|
|
}
|
|
}
|
|
|
|
/// Lightweight session monitor that fetches terminal sessions on-demand
|
|
@MainActor
|
|
@Observable
|
|
final class SessionMonitor {
|
|
static let shared = SessionMonitor()
|
|
|
|
private(set) var sessions: [String: ServerSessionInfo] = [:]
|
|
private(set) var lastError: Error?
|
|
|
|
private var lastFetch: Date?
|
|
private let cacheInterval: TimeInterval = 2.0
|
|
private let serverPort: Int
|
|
private var localAuthToken: String?
|
|
|
|
private init() {
|
|
let port = UserDefaults.standard.integer(forKey: "serverPort")
|
|
self.serverPort = port > 0 ? port : 4_020
|
|
}
|
|
|
|
/// Set the local auth token for server requests
|
|
func setLocalAuthToken(_ token: String?) {
|
|
self.localAuthToken = token
|
|
}
|
|
|
|
/// Number of running sessions
|
|
var sessionCount: Int {
|
|
sessions.values.count { $0.isRunning }
|
|
}
|
|
|
|
/// Get all sessions, using cache if available
|
|
func getSessions() async -> [String: ServerSessionInfo] {
|
|
// Use cache if available and fresh
|
|
if let lastFetch, Date().timeIntervalSince(lastFetch) < cacheInterval {
|
|
return sessions
|
|
}
|
|
|
|
await fetchSessions()
|
|
return sessions
|
|
}
|
|
|
|
/// Force refresh session data
|
|
func refresh() async {
|
|
lastFetch = nil
|
|
await fetchSessions()
|
|
}
|
|
|
|
// MARK: - Private Methods
|
|
|
|
private func fetchSessions() async {
|
|
do {
|
|
// Get current port (might have changed)
|
|
let port = UserDefaults.standard.integer(forKey: "serverPort")
|
|
let actualPort = port > 0 ? port : serverPort
|
|
|
|
guard let url = URL(string: "http://localhost:\(actualPort)/api/sessions") else {
|
|
throw URLError(.badURL)
|
|
}
|
|
|
|
var request = URLRequest(url: url, timeoutInterval: 3.0)
|
|
|
|
// Add Host header to ensure request is recognized as local
|
|
request.setValue("localhost", forHTTPHeaderField: "Host")
|
|
|
|
// Add local auth token if available
|
|
if let token = localAuthToken {
|
|
request.setValue(token, forHTTPHeaderField: "X-VibeTunnel-Local")
|
|
}
|
|
|
|
let (data, response) = try await URLSession.shared.data(for: request)
|
|
|
|
guard let httpResponse = response as? HTTPURLResponse,
|
|
httpResponse.statusCode == 200
|
|
else {
|
|
throw URLError(.badServerResponse)
|
|
}
|
|
|
|
let sessionsArray = try JSONDecoder().decode([ServerSessionInfo].self, from: data)
|
|
|
|
// Convert to dictionary
|
|
var sessionsDict: [String: ServerSessionInfo] = [:]
|
|
for session in sessionsArray {
|
|
sessionsDict[session.id] = session
|
|
}
|
|
|
|
self.sessions = sessionsDict
|
|
self.lastError = nil
|
|
self.lastFetch = Date()
|
|
|
|
// Update WindowTracker
|
|
WindowTracker.shared.updateFromSessions(sessionsArray)
|
|
} catch {
|
|
// Only update error if it's not a simple connection error
|
|
if !(error is URLError) {
|
|
self.lastError = error
|
|
}
|
|
self.sessions = [:]
|
|
self.lastFetch = Date() // Still update timestamp to avoid hammering
|
|
}
|
|
}
|
|
}
|