import Foundation struct WorkspaceServer: Codable, Equatable { let url: URL let profile: UUID static func normalize(_ input: String) -> URL? { let input = input.trimmingCharacters(in: .whitespacesAndNewlines) guard input.lowercased().hasPrefix("https://"), !input.contains("\\"), input.rangeOfCharacter(from: .controlCharacters) == nil, var components = URLComponents(string: input), components.scheme?.lowercased() == "https", let host = components.host, !host.isEmpty, components.user == nil, components.password == nil, components.query == nil, components.fragment == nil, components.port.map({ (1...65535).contains($0) }) ?? true else { return nil } components.scheme = "https" components.host = host.lowercased() if components.port == 443 { components.port = nil } if !components.path.hasSuffix("/") { components.path += "/" } return components.url } var dashboardURL: URL { url.appendingPathComponent("dashboard") } func allows(_ destination: URL) -> Bool { destination.scheme?.lowercased() == "https" && destination.host?.lowercased() == url.host?.lowercased() && (destination.port ?? 443) == (url.port ?? 443) && destination.user == nil && destination.password == nil } static func allowsExternal(_ destination: URL) -> Bool { ["https", "http"].contains(destination.scheme?.lowercased() ?? "") && destination.host?.isEmpty == false && (destination.port.map({ (1...65535).contains($0) }) ?? true) && destination.user == nil && destination.password == nil } static func selecting(_ url: URL, current: WorkspaceServer?) -> WorkspaceServer { current?.url == url ? current! : WorkspaceServer(url: url, profile: UUID()) } } struct WorkspaceConnection { private(set) var revision = 0 private(set) var ready = false private(set) var connecting = false mutating func begin() -> Int { revision += 1 connecting = true return revision } mutating func finish() { connecting = false ready = true } mutating func fail(_ expectedRevision: Int) -> Bool { guard expectedRevision == revision, connecting else { return false } connecting = false return true } }