vibetunnel/VibeTunnel/Core/Services/HTTPClientProtocol.swift
Peter Steinberger 70a8da5235 feat: enhance UI and automatic update handling
- Fix session count display to show on single line in menu bar
- Add conditional compilation to disable automatic updates in DEBUG mode
- Add "Open Dashboard" menu item that opens internal server URL
- Convert Help menu from popover to native macOS submenu style
- Enable automatic update downloads in Sparkle configuration
- Increase Advanced Settings tab height from 400 to 500 pixels
- Add Tailscale recommendation with clickable markdown link
- Fix Sendable protocol conformance issues throughout codebase
- Add ApplicationMover utility for app installation location management

These changes improve the overall user experience by making the UI more
intuitive and ensuring automatic updates work correctly in production
while being disabled during development.
2025-06-16 05:53:08 +02:00

82 lines
2.4 KiB
Swift

import Foundation
import HTTPTypes
/// Protocol for HTTP client abstraction to enable testing
public protocol HTTPClientProtocol {
func data(for request: HTTPRequest, body: Data?) async throws -> (Data, HTTPResponse)
}
/// Real HTTP client implementation
public final class HTTPClient: HTTPClientProtocol {
private let session: URLSession
public init(session: URLSession = .shared) {
self.session = session
}
public func data(for request: HTTPRequest, body: Data?) async throws -> (Data, HTTPResponse) {
var urlRequest = URLRequest(customHTTPRequest: request)
urlRequest.httpBody = body
let (data, response) = try await session.data(for: urlRequest)
guard let httpResponse = response as? HTTPURLResponse else {
throw HTTPClientError.invalidResponse
}
let httpTypesResponse = httpResponse.httpResponse
return (data, httpTypesResponse)
}
}
enum HTTPClientError: Error {
case invalidResponse
}
// MARK: - URLSession Extensions
extension URLRequest {
init(customHTTPRequest: HTTPRequest) {
// Reconstruct URL from components
var urlComponents = URLComponents()
urlComponents.scheme = customHTTPRequest.scheme
if let authority = customHTTPRequest.authority {
// Parse host and port from authority
let parts = authority.split(separator: ":", maxSplits: 1)
urlComponents.host = String(parts[0])
if parts.count > 1 {
urlComponents.port = Int(String(parts[1]))
}
}
urlComponents.path = customHTTPRequest.path ?? "/"
guard let url = urlComponents.url else {
fatalError("HTTPRequest must have valid URL components")
}
self.init(url: url)
self.httpMethod = customHTTPRequest.method.rawValue
// Copy headers
for field in customHTTPRequest.headerFields {
self.setValue(field.value, forHTTPHeaderField: field.name.rawName)
}
}
}
extension HTTPURLResponse {
var httpResponse: HTTPResponse {
let status = HTTPResponse.Status(code: statusCode)
var headerFields = HTTPFields()
for (key, value) in allHeaderFields {
if let name = key as? String, let fieldName = HTTPField.Name(name) {
headerFields[fieldName] = value as? String
}
}
return HTTPResponse(status: status, headerFields: headerFields)
}
}