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 } } enum NativeSettingsError: Error { case invalidRequest, incompatibleVersion, invalidServer } struct NativeSettingsRequest { let id: Int let action: String let payload: [String: Any] static func parse(_ body: String) throws -> NativeSettingsRequest { guard body.utf8.count <= 8192, let value = try JSONSerialization.jsonObject(with: Data(body.utf8)) as? [String: Any], Set(value.keys) == Set(["version", "id", "action", "payload"]), let version = value["version"] as? NSNumber, CFGetTypeID(version) != CFBooleanGetTypeID(), version == 1 else { throw NativeSettingsError.incompatibleVersion } guard let id = value["id"] as? NSNumber, CFGetTypeID(id) != CFBooleanGetTypeID(), id.doubleValue >= 0, id.doubleValue <= 9007199254740991, id.doubleValue.rounded() == id.doubleValue, let action = value["action"] as? String, let payload = value["payload"] as? [String: Any] else { throw NativeSettingsError.invalidRequest } switch action { case "getState", "settingsReady", "openSettings": guard payload.isEmpty else { throw NativeSettingsError.invalidRequest } case "setWorkspace": guard Set(payload.keys) == Set(["server"]), payload["server"] is String else { throw NativeSettingsError.invalidRequest } default: throw NativeSettingsError.invalidRequest } return NativeSettingsRequest(id: id.intValue, action: action, payload: payload) } }