mirror of
https://github.com/samsonjs/vibetunnel.git
synced 2026-04-27 15:17:38 +00:00
Kill leftover prototype
This commit is contained in:
parent
179d0db754
commit
2bc2c2f3c7
3 changed files with 0 additions and 562 deletions
|
|
@ -1,121 +0,0 @@
|
||||||
import CryptoKit
|
|
||||||
import Foundation
|
|
||||||
import HTTPTypes
|
|
||||||
import Hummingbird
|
|
||||||
import HummingbirdCore
|
|
||||||
import Logging
|
|
||||||
|
|
||||||
/// Custom HTTP header name for API key
|
|
||||||
extension HTTPField.Name {
|
|
||||||
static let xAPIKey = HTTPField.Name("X-API-Key")!
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Simple authentication middleware for the tunnel server
|
|
||||||
struct AuthenticationMiddleware<Context: RequestContext>: RouterMiddleware {
|
|
||||||
private let logger = Logger(label: "VibeTunnel.AuthMiddleware")
|
|
||||||
private let bearerPrefix = "Bearer "
|
|
||||||
|
|
||||||
/// In production, this should be stored securely and configurable
|
|
||||||
private let validApiKeys: Set<String>
|
|
||||||
|
|
||||||
init() {
|
|
||||||
// Load API keys from storage
|
|
||||||
var apiKeys = APIKeyManager.loadStoredAPIKeys()
|
|
||||||
|
|
||||||
if apiKeys.isEmpty {
|
|
||||||
// Generate a default API key for development
|
|
||||||
let defaultKey = Self.generateAPIKey()
|
|
||||||
apiKeys = [defaultKey]
|
|
||||||
APIKeyManager.saveAPIKeys(apiKeys)
|
|
||||||
logger.info("Authentication initialized with new API key: \(defaultKey)")
|
|
||||||
} else {
|
|
||||||
logger.info("Authentication initialized with \(apiKeys.count) stored API key(s)")
|
|
||||||
}
|
|
||||||
|
|
||||||
self.validApiKeys = apiKeys
|
|
||||||
}
|
|
||||||
|
|
||||||
init(apiKeys: Set<String>) {
|
|
||||||
self.validApiKeys = apiKeys
|
|
||||||
}
|
|
||||||
|
|
||||||
func handle(
|
|
||||||
_ request: Request,
|
|
||||||
context: Context,
|
|
||||||
next: (Request, Context) async throws -> Response
|
|
||||||
)
|
|
||||||
async throws -> Response
|
|
||||||
{
|
|
||||||
// Skip authentication for health check and WebSocket upgrade
|
|
||||||
if request.uri.path == "/health" || request.headers[HTTPField.Name.upgrade] == "websocket" {
|
|
||||||
return try await next(request, context)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check for API key in header
|
|
||||||
if let apiKey = request.headers[HTTPField.Name.xAPIKey] {
|
|
||||||
if validApiKeys.contains(apiKey) {
|
|
||||||
return try await next(request, context)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check for Bearer token
|
|
||||||
if let authorization = request.headers[HTTPField.Name.authorization],
|
|
||||||
authorization.hasPrefix(bearerPrefix)
|
|
||||||
{
|
|
||||||
let token = String(authorization.dropFirst(bearerPrefix.count))
|
|
||||||
if validApiKeys.contains(token) {
|
|
||||||
return try await next(request, context)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// No valid authentication found
|
|
||||||
logger.warning("Unauthorized request to \(request.uri.path)")
|
|
||||||
throw HTTPError(.unauthorized, message: "Invalid or missing API key")
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Generate a secure API key
|
|
||||||
static func generateAPIKey() -> String {
|
|
||||||
let randomBytes = SymmetricKey(size: .bits256)
|
|
||||||
let data = randomBytes.withUnsafeBytes { Data($0) }
|
|
||||||
return data.base64EncodedString()
|
|
||||||
.replacingOccurrences(of: "+", with: "-")
|
|
||||||
.replacingOccurrences(of: "/", with: "_")
|
|
||||||
.replacingOccurrences(of: "=", with: "")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// API Key management utilities
|
|
||||||
enum APIKeyManager {
|
|
||||||
static let apiKeyStorageKey = "VibeTunnel.APIKeys"
|
|
||||||
|
|
||||||
static func loadStoredAPIKeys() -> Set<String> {
|
|
||||||
guard let data = UserDefaults.standard.data(forKey: apiKeyStorageKey),
|
|
||||||
let keys = try? JSONDecoder().decode(Set<String>.self, from: data)
|
|
||||||
else {
|
|
||||||
// Generate and store a default key if none exists
|
|
||||||
let defaultKey = AuthenticationMiddleware<BasicRequestContext>.generateAPIKey()
|
|
||||||
let keys = Set([defaultKey])
|
|
||||||
saveAPIKeys(keys)
|
|
||||||
return keys
|
|
||||||
}
|
|
||||||
return keys
|
|
||||||
}
|
|
||||||
|
|
||||||
static func saveAPIKeys(_ keys: Set<String>) {
|
|
||||||
if let data = try? JSONEncoder().encode(keys) {
|
|
||||||
UserDefaults.standard.set(data, forKey: apiKeyStorageKey)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static func addAPIKey(_ key: String) {
|
|
||||||
var keys = loadStoredAPIKeys()
|
|
||||||
keys.insert(key)
|
|
||||||
saveAPIKeys(keys)
|
|
||||||
}
|
|
||||||
|
|
||||||
static func removeAPIKey(_ key: String) {
|
|
||||||
var keys = loadStoredAPIKeys()
|
|
||||||
keys.remove(key)
|
|
||||||
saveAPIKeys(keys)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,247 +0,0 @@
|
||||||
import AppKit
|
|
||||||
import Combine
|
|
||||||
import Foundation
|
|
||||||
import HTTPTypes
|
|
||||||
import Hummingbird
|
|
||||||
import HummingbirdCore
|
|
||||||
import Logging
|
|
||||||
import NIOCore
|
|
||||||
import os
|
|
||||||
|
|
||||||
// MARK: - Response Models
|
|
||||||
|
|
||||||
/// Server info response
|
|
||||||
struct ServerInfoResponse: ResponseCodable {
|
|
||||||
let name: String
|
|
||||||
let version: String
|
|
||||||
let uptime: TimeInterval
|
|
||||||
let sessions: Int
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Main tunnel server implementation using Hummingbird
|
|
||||||
@MainActor
|
|
||||||
final class TunnelServer: ObservableObject {
|
|
||||||
private let port: Int
|
|
||||||
private let logger = Logger(label: "VibeTunnel.TunnelServer")
|
|
||||||
private var app: Application<Router<BasicRequestContext>.Responder>?
|
|
||||||
private let terminalManager = TerminalManager()
|
|
||||||
|
|
||||||
@Published var isRunning = false
|
|
||||||
@Published var lastError: Error?
|
|
||||||
@Published var connectedClients = 0
|
|
||||||
|
|
||||||
init(port: Int = 8_080) {
|
|
||||||
self.port = port
|
|
||||||
}
|
|
||||||
|
|
||||||
func start() async throws {
|
|
||||||
logger.info("Starting tunnel server on port \(port)")
|
|
||||||
|
|
||||||
do {
|
|
||||||
// Build the Hummingbird application
|
|
||||||
let app = try await buildApplication()
|
|
||||||
self.app = app
|
|
||||||
|
|
||||||
// Start the server
|
|
||||||
try await app.run()
|
|
||||||
|
|
||||||
await MainActor.run {
|
|
||||||
self.isRunning = true
|
|
||||||
}
|
|
||||||
} catch {
|
|
||||||
await MainActor.run {
|
|
||||||
self.lastError = error
|
|
||||||
self.isRunning = false
|
|
||||||
}
|
|
||||||
throw error
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func stop() async {
|
|
||||||
logger.info("Stopping tunnel server")
|
|
||||||
|
|
||||||
// In Hummingbird 2.x, the application lifecycle is managed differently
|
|
||||||
// Setting app to nil will trigger cleanup when it's deallocated
|
|
||||||
self.app = nil
|
|
||||||
|
|
||||||
await MainActor.run {
|
|
||||||
self.isRunning = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private func buildApplication() async throws -> Application<Router<BasicRequestContext>.Responder> {
|
|
||||||
// Create router
|
|
||||||
let router = Router<BasicRequestContext>()
|
|
||||||
|
|
||||||
// Add middleware
|
|
||||||
router.add(middleware: LogRequestsMiddleware(.info))
|
|
||||||
router.add(middleware: CORSMiddleware(
|
|
||||||
allowOrigin: .all,
|
|
||||||
allowHeaders: [.contentType, .authorization],
|
|
||||||
allowMethods: [.get, .post, .delete, .options]
|
|
||||||
))
|
|
||||||
router.add(middleware: AuthenticationMiddleware(apiKeys: APIKeyManager.loadStoredAPIKeys()))
|
|
||||||
|
|
||||||
// Configure routes
|
|
||||||
configureRoutes(router)
|
|
||||||
|
|
||||||
// Add WebSocket routes
|
|
||||||
// TODO: Uncomment when HummingbirdWebSocket package is added
|
|
||||||
// router.addWebSocketRoutes(terminalManager: terminalManager)
|
|
||||||
|
|
||||||
// Create application configuration
|
|
||||||
let configuration = ApplicationConfiguration(
|
|
||||||
address: .hostname("127.0.0.1", port: port),
|
|
||||||
serverName: "VibeTunnel"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Create and configure the application
|
|
||||||
let app = Application(
|
|
||||||
responder: router.buildResponder(),
|
|
||||||
configuration: configuration,
|
|
||||||
logger: logger
|
|
||||||
)
|
|
||||||
|
|
||||||
// Add cleanup task
|
|
||||||
// Start cleanup task
|
|
||||||
Task {
|
|
||||||
while !Task.isCancelled {
|
|
||||||
await terminalManager.cleanupInactiveSessions(olderThan: 30)
|
|
||||||
try? await Task.sleep(nanoseconds: 5 * 60 * 1_000_000_000) // 5 minutes
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return app
|
|
||||||
}
|
|
||||||
|
|
||||||
private func configureRoutes(_ router: Router<BasicRequestContext>) {
|
|
||||||
// Health check endpoint
|
|
||||||
router.get("/health") { _, _ -> HTTPResponse.Status in
|
|
||||||
.ok
|
|
||||||
}
|
|
||||||
|
|
||||||
// Server info endpoint
|
|
||||||
router.get("/info") { _, _ async -> ServerInfoResponse in
|
|
||||||
let sessionCount = await self.terminalManager.listSessions().count
|
|
||||||
return ServerInfoResponse(
|
|
||||||
name: "VibeTunnel",
|
|
||||||
version: Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String ?? "1.0.0",
|
|
||||||
uptime: ProcessInfo.processInfo.systemUptime,
|
|
||||||
sessions: sessionCount
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Session management endpoints
|
|
||||||
let sessions = router.group("sessions")
|
|
||||||
|
|
||||||
// List all sessions
|
|
||||||
sessions.get("/") { _, _ async -> ListSessionsResponse in
|
|
||||||
let sessions = await self.terminalManager.listSessions()
|
|
||||||
let sessionInfos = sessions.map { session in
|
|
||||||
SessionInfo(
|
|
||||||
id: session.id.uuidString,
|
|
||||||
createdAt: session.createdAt,
|
|
||||||
lastActivity: session.lastActivity,
|
|
||||||
isActive: session.isActive
|
|
||||||
)
|
|
||||||
}
|
|
||||||
return ListSessionsResponse(sessions: sessionInfos)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create new session
|
|
||||||
sessions.post("/") { request, context async throws -> CreateSessionResponse in
|
|
||||||
let createRequest = try await request.decode(as: CreateSessionRequest.self, context: context)
|
|
||||||
let session = try await self.terminalManager.createSession(request: createRequest)
|
|
||||||
|
|
||||||
return CreateSessionResponse(
|
|
||||||
sessionId: session.id.uuidString,
|
|
||||||
createdAt: session.createdAt
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get session info
|
|
||||||
sessions.get(":sessionId") { _, context async throws -> SessionInfo in
|
|
||||||
guard let sessionIdString = context.parameters.get("sessionId", as: String.self),
|
|
||||||
let sessionId = UUID(uuidString: sessionIdString),
|
|
||||||
let session = await self.terminalManager.getSession(id: sessionId)
|
|
||||||
else {
|
|
||||||
throw HTTPError(.notFound)
|
|
||||||
}
|
|
||||||
|
|
||||||
return SessionInfo(
|
|
||||||
id: session.id.uuidString,
|
|
||||||
createdAt: session.createdAt,
|
|
||||||
lastActivity: session.lastActivity,
|
|
||||||
isActive: session.isActive
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Close session
|
|
||||||
sessions.delete(":sessionId") { _, context async throws -> HTTPResponse.Status in
|
|
||||||
guard let sessionIdString = context.parameters.get("sessionId", as: String.self),
|
|
||||||
let sessionId = UUID(uuidString: sessionIdString)
|
|
||||||
else {
|
|
||||||
throw HTTPError(.badRequest)
|
|
||||||
}
|
|
||||||
|
|
||||||
await self.terminalManager.closeSession(id: sessionId)
|
|
||||||
return .noContent
|
|
||||||
}
|
|
||||||
|
|
||||||
// Command execution endpoint
|
|
||||||
router.post("/execute") { request, context async throws -> CommandResponse in
|
|
||||||
let commandRequest = try await request.decode(as: CommandRequest.self, context: context)
|
|
||||||
|
|
||||||
guard let sessionId = UUID(uuidString: commandRequest.sessionId) else {
|
|
||||||
throw HTTPError(.badRequest, message: "Invalid session ID")
|
|
||||||
}
|
|
||||||
|
|
||||||
do {
|
|
||||||
let (output, error) = try await self.terminalManager.executeCommand(
|
|
||||||
sessionId: sessionId,
|
|
||||||
command: commandRequest.command
|
|
||||||
)
|
|
||||||
|
|
||||||
return CommandResponse(
|
|
||||||
sessionId: commandRequest.sessionId,
|
|
||||||
output: output.isEmpty ? nil : output,
|
|
||||||
error: error.isEmpty ? nil : error,
|
|
||||||
exitCode: nil,
|
|
||||||
timestamp: Date()
|
|
||||||
)
|
|
||||||
} catch {
|
|
||||||
throw HTTPError(.internalServerError, message: error.localizedDescription)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: - Integration with AppDelegate
|
|
||||||
|
|
||||||
extension AppDelegate {
|
|
||||||
func startTunnelServer() {
|
|
||||||
Task {
|
|
||||||
do {
|
|
||||||
let port = UserDefaults.standard.integer(forKey: "serverPort")
|
|
||||||
let tunnelServer = TunnelServer(port: port > 0 ? port : 8_080)
|
|
||||||
|
|
||||||
// Store reference if needed
|
|
||||||
// self.tunnelServer = tunnelServer
|
|
||||||
|
|
||||||
try await tunnelServer.start()
|
|
||||||
} catch {
|
|
||||||
let logger = Logger(label: "VibeTunnel.AppDelegate")
|
|
||||||
logger.error("Failed to start tunnel server: \(error)")
|
|
||||||
|
|
||||||
// Show error alert
|
|
||||||
await MainActor.run {
|
|
||||||
let alert = NSAlert()
|
|
||||||
alert.messageText = "Failed to Start Server"
|
|
||||||
alert.informativeText = error.localizedDescription
|
|
||||||
alert.alertStyle = .critical
|
|
||||||
alert.runModal()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,194 +0,0 @@
|
||||||
import Foundation
|
|
||||||
import Hummingbird
|
|
||||||
import HummingbirdCore
|
|
||||||
import NIOCore
|
|
||||||
|
|
||||||
// import NIOWebSocket // TODO: This is available in swift-nio package
|
|
||||||
import Logging
|
|
||||||
|
|
||||||
/// WebSocket message types for terminal communication
|
|
||||||
public enum WSMessageType: String, Codable {
|
|
||||||
case connect
|
|
||||||
case command
|
|
||||||
case output
|
|
||||||
case error
|
|
||||||
case ping
|
|
||||||
case pong
|
|
||||||
case close
|
|
||||||
}
|
|
||||||
|
|
||||||
/// WebSocket message structure
|
|
||||||
public struct WSMessage: Codable {
|
|
||||||
public let type: WSMessageType
|
|
||||||
public let sessionId: String?
|
|
||||||
public let data: String?
|
|
||||||
public let timestamp: Date
|
|
||||||
|
|
||||||
public init(type: WSMessageType, sessionId: String? = nil, data: String? = nil) {
|
|
||||||
self.type = type
|
|
||||||
self.sessionId = sessionId
|
|
||||||
self.data = data
|
|
||||||
self.timestamp = Date()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: Enable when HummingbirdWebSocket package is added
|
|
||||||
// /// Handles WebSocket connections for real-time terminal communication
|
|
||||||
// final class WebSocketHandler {
|
|
||||||
// private let terminalManager: TerminalManager
|
|
||||||
// private let logger = Logger(label: "VibeTunnel.WebSocketHandler")
|
|
||||||
// private var activeConnections: [UUID: WebSocketHandler.Connection] = [:]
|
|
||||||
//
|
|
||||||
// init(terminalManager: TerminalManager) {
|
|
||||||
// self.terminalManager = terminalManager
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// /// Handle incoming WebSocket connection
|
|
||||||
// func handle(ws: WebSocket, context: some RequestContext) async {
|
|
||||||
// let connectionId = UUID()
|
|
||||||
// let connection = Connection(id: connectionId, websocket: ws)
|
|
||||||
//
|
|
||||||
// await MainActor.run {
|
|
||||||
// activeConnections[connectionId] = connection
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// logger.info("WebSocket connection established: \(connectionId)")
|
|
||||||
//
|
|
||||||
// // Set up message handlers
|
|
||||||
// ws.onText { [weak self] ws, text in
|
|
||||||
// await self?.handleTextMessage(text, connection: connection)
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// ws.onBinary { [weak self] ws, buffer in
|
|
||||||
// // Handle binary data if needed
|
|
||||||
// self?.logger.debug("Received binary data: \(buffer.readableBytes) bytes")
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// ws.onClose { [weak self] closeCode in
|
|
||||||
// await self?.handleClose(connection: connection)
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// // Send initial connection acknowledgment
|
|
||||||
// await sendMessage(WSMessage(type: .connect, data: "Connected to VibeTunnel"), to: connection)
|
|
||||||
//
|
|
||||||
// // Keep connection alive with periodic pings
|
|
||||||
// Task {
|
|
||||||
// while !Task.isCancelled && !connection.isClosed {
|
|
||||||
// await sendMessage(WSMessage(type: .ping), to: connection)
|
|
||||||
// try? await Task.sleep(nanoseconds: 30 * 1_000_000_000) // 30 seconds
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// private func handleTextMessage(_ text: String, connection: Connection) async {
|
|
||||||
// guard let data = text.data(using: .utf8),
|
|
||||||
// let message = try? JSONDecoder().decode(WSMessage.self, from: data) else {
|
|
||||||
// logger.error("Failed to decode WebSocket message: \(text)")
|
|
||||||
// await sendError("Invalid message format", to: connection)
|
|
||||||
// return
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// switch message.type {
|
|
||||||
// case .connect:
|
|
||||||
// // Handle session connection
|
|
||||||
// if let sessionId = message.sessionId,
|
|
||||||
// let uuid = UUID(uuidString: sessionId) {
|
|
||||||
// connection.sessionId = uuid
|
|
||||||
// await sendMessage(WSMessage(type: .output, sessionId: sessionId, data: "Session connected"), to:
|
|
||||||
// connection)
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// case .command:
|
|
||||||
// // Execute command in terminal session
|
|
||||||
// guard let sessionId = connection.sessionId,
|
|
||||||
// let command = message.data else {
|
|
||||||
// await sendError("Session ID and command required", to: connection)
|
|
||||||
// return
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// do {
|
|
||||||
// let (output, error) = try await terminalManager.executeCommand(sessionId: sessionId, command: command)
|
|
||||||
//
|
|
||||||
// if !output.isEmpty {
|
|
||||||
// await sendMessage(WSMessage(type: .output, sessionId: sessionId.uuidString, data: output), to:
|
|
||||||
// connection)
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// if !error.isEmpty {
|
|
||||||
// await sendMessage(WSMessage(type: .error, sessionId: sessionId.uuidString, data: error), to:
|
|
||||||
// connection)
|
|
||||||
// }
|
|
||||||
// } catch {
|
|
||||||
// await sendError(error.localizedDescription, to: connection)
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// case .ping:
|
|
||||||
// // Respond to ping with pong
|
|
||||||
// await sendMessage(WSMessage(type: .pong), to: connection)
|
|
||||||
//
|
|
||||||
// case .close:
|
|
||||||
// // Close the session
|
|
||||||
// if let sessionId = connection.sessionId {
|
|
||||||
// await terminalManager.closeSession(id: sessionId)
|
|
||||||
// }
|
|
||||||
// try? await connection.websocket.close()
|
|
||||||
//
|
|
||||||
// default:
|
|
||||||
// logger.warning("Unhandled message type: \(message.type)")
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// private func handleClose(connection: Connection) async {
|
|
||||||
// logger.info("WebSocket connection closed: \(connection.id)")
|
|
||||||
//
|
|
||||||
// await MainActor.run {
|
|
||||||
// activeConnections.removeValue(forKey: connection.id)
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// // Clean up associated session if any
|
|
||||||
// if let sessionId = connection.sessionId {
|
|
||||||
// await terminalManager.closeSession(id: sessionId)
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// connection.isClosed = true
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// private func sendMessage(_ message: WSMessage, to connection: Connection) async {
|
|
||||||
// do {
|
|
||||||
// let data = try JSONEncoder().encode(message)
|
|
||||||
// let text = String(data: data, encoding: .utf8) ?? "{}"
|
|
||||||
// try await connection.websocket.send(text: text)
|
|
||||||
// } catch {
|
|
||||||
// logger.error("Failed to send WebSocket message: \(error)")
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// private func sendError(_ error: String, to connection: Connection) async {
|
|
||||||
// await sendMessage(WSMessage(type: .error, data: error), to: connection)
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// /// WebSocket connection wrapper
|
|
||||||
// class Connection {
|
|
||||||
// let id: UUID
|
|
||||||
// let websocket: WebSocket
|
|
||||||
// var sessionId: UUID?
|
|
||||||
// var isClosed = false
|
|
||||||
//
|
|
||||||
// init(id: UUID, websocket: WebSocket) {
|
|
||||||
// self.id = id
|
|
||||||
// self.websocket = websocket
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// /// Extension to add WebSocket routes to the router
|
|
||||||
// extension RouterBuilder {
|
|
||||||
// mutating func addWebSocketRoutes(terminalManager: TerminalManager) {
|
|
||||||
// let wsHandler = WebSocketHandler(terminalManager: terminalManager)
|
|
||||||
//
|
|
||||||
// // WebSocket endpoint for terminal streaming
|
|
||||||
// ws("/ws/terminal") { request, ws, context in
|
|
||||||
// await wsHandler.handle(ws: ws, context: context)
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
Loading…
Reference in a new issue