mirror of
https://github.com/samsonjs/vibetunnel.git
synced 2026-04-24 14:47:39 +00:00
211 lines
6.2 KiB
Swift
211 lines
6.2 KiB
Swift
import Foundation
|
|
|
|
/// Control message protocol for unified Unix socket communication
|
|
enum ControlProtocol {
|
|
// MARK: - Message Types
|
|
|
|
enum MessageType: String, Codable {
|
|
case request
|
|
case response
|
|
case event
|
|
}
|
|
|
|
enum Category: String, Codable {
|
|
case terminal
|
|
case git
|
|
case system
|
|
}
|
|
|
|
// MARK: - Control Message Structure (with generic payload support)
|
|
|
|
struct ControlMessage<Payload: Codable>: Codable {
|
|
let id: String
|
|
let type: MessageType
|
|
let category: Category
|
|
let action: String
|
|
let payload: Payload?
|
|
let sessionId: String?
|
|
let error: String?
|
|
|
|
init(
|
|
id: String = UUID().uuidString,
|
|
type: MessageType,
|
|
category: Category,
|
|
action: String,
|
|
payload: Payload? = nil,
|
|
sessionId: String? = nil,
|
|
error: String? = nil
|
|
) {
|
|
self.id = id
|
|
self.type = type
|
|
self.category = category
|
|
self.action = action
|
|
self.payload = payload
|
|
self.sessionId = sessionId
|
|
self.error = error
|
|
}
|
|
}
|
|
|
|
// MARK: - Base message for runtime dispatch
|
|
|
|
protocol AnyControlMessage {
|
|
var id: String { get }
|
|
var type: MessageType { get }
|
|
var category: Category { get }
|
|
var action: String { get }
|
|
var sessionId: String? { get }
|
|
var error: String? { get }
|
|
}
|
|
|
|
// MARK: - Type aliases for common message types
|
|
|
|
typealias TerminalSpawnRequestMessage = ControlMessage<TerminalSpawnRequest>
|
|
typealias TerminalSpawnResponseMessage = ControlMessage<TerminalSpawnResponse>
|
|
typealias SystemReadyMessage = ControlMessage<SystemReadyEvent>
|
|
typealias SystemPingRequestMessage = ControlMessage<SystemPingRequest>
|
|
typealias SystemPingResponseMessage = ControlMessage<SystemPingResponse>
|
|
|
|
// MARK: - Convenience builders for specific message types
|
|
|
|
/// Terminal messages
|
|
static func terminalSpawnRequest(
|
|
sessionId: String,
|
|
workingDirectory: String? = nil,
|
|
command: String? = nil,
|
|
terminalPreference: String? = nil
|
|
)
|
|
-> TerminalSpawnRequestMessage
|
|
{
|
|
ControlMessage(
|
|
type: .request,
|
|
category: .terminal,
|
|
action: "spawn",
|
|
payload: TerminalSpawnRequest(
|
|
sessionId: sessionId,
|
|
workingDirectory: workingDirectory,
|
|
command: command,
|
|
terminalPreference: terminalPreference
|
|
),
|
|
sessionId: sessionId
|
|
)
|
|
}
|
|
|
|
/// Build a spawn response
|
|
/// NOTE: Error Duplication Pattern
|
|
/// Both top-level error and payload error fields are set intentionally:
|
|
/// - Top-level error: Indicates transport/protocol-level errors (malformed request, handler not found)
|
|
/// - Payload error: Indicates application-level errors (spawn failed due to permissions)
|
|
/// This separation allows clients to distinguish between different error types.
|
|
static func terminalSpawnResponse(
|
|
to request: TerminalSpawnRequestMessage,
|
|
success: Bool,
|
|
pid: Int? = nil,
|
|
error: String? = nil
|
|
)
|
|
-> TerminalSpawnResponseMessage
|
|
{
|
|
ControlMessage(
|
|
id: request.id,
|
|
type: .response,
|
|
category: .terminal,
|
|
action: "spawn",
|
|
payload: TerminalSpawnResponse(success: success, pid: pid, error: error),
|
|
sessionId: request.sessionId,
|
|
error: error
|
|
)
|
|
}
|
|
|
|
/// System messages
|
|
static func systemReadyEvent() -> SystemReadyMessage {
|
|
ControlMessage(
|
|
type: .event,
|
|
category: .system,
|
|
action: "ready",
|
|
payload: SystemReadyEvent()
|
|
)
|
|
}
|
|
|
|
static func systemPingRequest() -> SystemPingRequestMessage {
|
|
ControlMessage(
|
|
type: .request,
|
|
category: .system,
|
|
action: "ping",
|
|
payload: SystemPingRequest()
|
|
)
|
|
}
|
|
|
|
static func systemPingResponse(
|
|
to request: SystemPingRequestMessage
|
|
)
|
|
-> SystemPingResponseMessage
|
|
{
|
|
ControlMessage(
|
|
id: request.id,
|
|
type: .response,
|
|
category: .system,
|
|
action: "ping",
|
|
payload: SystemPingResponse()
|
|
)
|
|
}
|
|
|
|
// MARK: - Message Serialization
|
|
|
|
static func encode(_ message: ControlMessage<some Codable>) throws -> Data {
|
|
let encoder = JSONEncoder()
|
|
return try encoder.encode(message)
|
|
}
|
|
|
|
static func decode<T: Codable>(_ data: Data, as messageType: ControlMessage<T>.Type) throws -> ControlMessage<T> {
|
|
let decoder = JSONDecoder()
|
|
return try decoder.decode(messageType, from: data)
|
|
}
|
|
|
|
/// Special encoder for messages with [String: Any] payloads
|
|
static func encodeWithDictionaryPayload(
|
|
id: String = UUID().uuidString,
|
|
type: MessageType,
|
|
category: Category,
|
|
action: String,
|
|
payload: [String: Any]? = nil,
|
|
sessionId: String? = nil,
|
|
error: String? = nil
|
|
)
|
|
throws -> Data
|
|
{
|
|
var dict: [String: Any] = [
|
|
"id": id,
|
|
"type": type.rawValue,
|
|
"category": category.rawValue,
|
|
"action": action
|
|
]
|
|
|
|
if let payload {
|
|
dict["payload"] = payload
|
|
}
|
|
if let sessionId {
|
|
dict["sessionId"] = sessionId
|
|
}
|
|
if let error {
|
|
dict["error"] = error
|
|
}
|
|
|
|
return try JSONSerialization.data(withJSONObject: dict)
|
|
}
|
|
|
|
/// For handlers that need to decode specific message types based on action
|
|
static func decodeTerminalSpawnRequest(_ data: Data) throws -> TerminalSpawnRequestMessage {
|
|
try decode(data, as: TerminalSpawnRequestMessage.self)
|
|
}
|
|
|
|
static func decodeSystemPingRequest(_ data: Data) throws -> SystemPingRequestMessage {
|
|
try decode(data, as: SystemPingRequestMessage.self)
|
|
}
|
|
|
|
// Empty payload for messages that don't need data
|
|
struct EmptyPayload: Codable {}
|
|
typealias EmptyMessage = ControlMessage<EmptyPayload>
|
|
}
|
|
|
|
// MARK: - Protocol Conformance
|
|
|
|
extension ControlProtocol.ControlMessage: ControlProtocol.AnyControlMessage {}
|