Add SwiftUI + Network Extension (LibXray/hev), parse Clash Meta YAML from subscription URLs into vless/trojan/hy2 share links, keep the selected server on connect, and ship Navis-3.8.2.13.ipa with versioned changelog. Co-authored-by: Cursor <cursoragent@cursor.com>
91 lines
2.8 KiB
Swift
91 lines
2.8 KiB
Swift
import Foundation
|
|
|
|
enum Proto: String, Codable, Sendable {
|
|
case vless
|
|
case vmess
|
|
case trojan
|
|
case hy2
|
|
case naive
|
|
case awg
|
|
case unknown
|
|
|
|
var label: String { rawValue }
|
|
|
|
static func detect(_ uri: String) -> Proto {
|
|
let lower = uri.trimmingCharacters(in: .whitespacesAndNewlines).lowercased()
|
|
if lower.hasPrefix("vless://") { return .vless }
|
|
if lower.hasPrefix("vmess://") { return .vmess }
|
|
if lower.hasPrefix("trojan://") { return .trojan }
|
|
if lower.hasPrefix("hysteria2://") || lower.hasPrefix("hy2://") { return .hy2 }
|
|
if lower.hasPrefix("naive+") || lower.hasPrefix("naive://") { return .naive }
|
|
// Desktop DetectProtocol: bare http(s)/quic with userinfo → naive
|
|
if lower.hasPrefix("https://") || lower.hasPrefix("http://") || lower.hasPrefix("quic://") {
|
|
return .naive
|
|
}
|
|
if lower.hasPrefix("awg://") || lower.hasPrefix("amneziawg://") || lower.hasPrefix("wireguard://") {
|
|
return .awg
|
|
}
|
|
if lower.localizedCaseInsensitiveContains("[Interface]") && lower.contains("Jc") {
|
|
return .awg
|
|
}
|
|
return .unknown
|
|
}
|
|
|
|
/// Protocols with a working in-process core in the current iOS appex.
|
|
var supportedOnIOS: Bool {
|
|
switch self {
|
|
case .vless, .vmess, .trojan:
|
|
return true
|
|
case .hy2:
|
|
// YAML builder ready; runtime needs unified NavisCore (LibXray Go runtime conflict).
|
|
return false
|
|
case .naive, .awg, .unknown:
|
|
return false
|
|
}
|
|
}
|
|
|
|
var iosUnsupportedReason: String? {
|
|
switch self {
|
|
case .hy2:
|
|
return "Hysteria2: пока используйте VLESS/VMess/Trojan (единый NavisCore для Hy2 — следующий шаг)"
|
|
case .naive:
|
|
return "NaiveProxy на iOS недоступен (Chromium-ядро)"
|
|
case .awg:
|
|
return "AmneziaWG на iOS — следующим этапом"
|
|
case .unknown:
|
|
return "Неизвестный протокол"
|
|
default:
|
|
return nil
|
|
}
|
|
}
|
|
}
|
|
|
|
struct Profile: Identifiable, Codable, Equatable, Sendable {
|
|
var id: String
|
|
var name: String
|
|
var uri: String
|
|
var host: String
|
|
|
|
var protocolKind: Proto { Proto.detect(uri) }
|
|
|
|
init(id: String, name: String, uri: String, host: String = "") {
|
|
self.id = id
|
|
self.name = name
|
|
self.uri = uri
|
|
self.host = host.isEmpty ? LinkParser.hostOf(uri) : host
|
|
}
|
|
}
|
|
|
|
struct PingResult: Identifiable, Sendable {
|
|
var id: String
|
|
var ms: Int64 = -1
|
|
var ok: Bool = false
|
|
var error: String?
|
|
}
|
|
|
|
struct TunnelConfig: Codable, Sendable {
|
|
var name: String
|
|
var uri: String
|
|
var startedAt: Double
|
|
}
|