mirror of
https://github.com/samsonjs/vibetunnel.git
synced 2026-04-25 14:57:37 +00:00
- 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.
82 lines
2.4 KiB
Swift
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)
|
|
}
|
|
}
|