release: 1.4.4 — channel «Облако» (WebSocket via KeenDNS cloud to own Keenetic): wsgw gateway, Android 1.4.4, iOS, docs/cloud.md
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -21,6 +21,10 @@ final class VPNController: ObservableObject {
|
||||
@Published var conf = UserDefaults.standard.string(forKey: "conf") ?? ""
|
||||
@Published var ruDirect = UserDefaults.standard.object(forKey: "ruDirect") as? Bool ?? true { didSet { saveOptions() } }
|
||||
@Published var transportMode = UserDefaults.standard.string(forKey: "transport") ?? "auto" { didSet { saveOptions() } }
|
||||
/// Channel «Облако»: wss:// of the user's Keenetic (empty -> "# pca-ws" from the link).
|
||||
@Published var wsURL = UserDefaults.standard.string(forKey: "wsURL") ?? "" { didSet { saveOptions() } }
|
||||
/// Imported servers: [["name": …, "conf": …]]; name/conf above mirror the selected one.
|
||||
@Published var servers = UserDefaults.standard.array(forKey: "servers") as? [[String: String]] ?? []
|
||||
@Published var status: NEVPNStatus = .invalid
|
||||
@Published var transport = ""
|
||||
@Published var message = ""
|
||||
@@ -29,6 +33,7 @@ final class VPNController: ObservableObject {
|
||||
private var timer: Timer?
|
||||
|
||||
init() {
|
||||
if servers.isEmpty && !conf.isEmpty { servers = [["name": name, "conf": conf]] } // pre-list single profile
|
||||
Task { await load() }
|
||||
NotificationCenter.default.addObserver(forName: .NEVPNStatusDidChange, object: nil, queue: .main) { [weak self] _ in
|
||||
Task { @MainActor in self?.status = self?.manager?.connection.status ?? .invalid }
|
||||
@@ -49,17 +54,40 @@ final class VPNController: ObservableObject {
|
||||
|
||||
func importLink(_ link: String) {
|
||||
guard let parsed = PhobosProfile.fromLink(link) else { message = "Это не ссылка phobos:// из панели"; return }
|
||||
conf = parsed.profile.raw
|
||||
name = parsed.name
|
||||
servers.removeAll { $0["name"] == parsed.name } // same name = updated link
|
||||
servers.append(["name": parsed.name, "conf": parsed.profile.raw])
|
||||
select(parsed.name)
|
||||
message = "Профиль «\(name)» импортирован"
|
||||
}
|
||||
|
||||
func select(_ server: String) {
|
||||
guard let s = servers.first(where: { $0["name"] == server }) else { return }
|
||||
name = server
|
||||
conf = s["conf"] ?? ""
|
||||
persist()
|
||||
Task { await save(reconnect: isOn) }
|
||||
}
|
||||
|
||||
func remove(_ server: String) {
|
||||
servers.removeAll { $0["name"] == server }
|
||||
if server == name {
|
||||
if let next = servers.first?["name"] { select(next); return }
|
||||
if isOn { manager?.connection.stopVPNTunnel() }
|
||||
name = ""; conf = ""
|
||||
}
|
||||
persist()
|
||||
}
|
||||
|
||||
private func persist() {
|
||||
UserDefaults.standard.set(servers, forKey: "servers")
|
||||
UserDefaults.standard.set(conf, forKey: "conf")
|
||||
UserDefaults.standard.set(name, forKey: "name")
|
||||
message = "Профиль «\(name)» импортирован"
|
||||
Task { await save(reconnect: isOn) }
|
||||
}
|
||||
|
||||
private func saveOptions() {
|
||||
UserDefaults.standard.set(ruDirect, forKey: "ruDirect")
|
||||
UserDefaults.standard.set(transportMode, forKey: "transport")
|
||||
UserDefaults.standard.set(wsURL, forKey: "wsURL")
|
||||
Task { await save(reconnect: isOn) }
|
||||
}
|
||||
|
||||
@@ -70,7 +98,7 @@ final class VPNController: ObservableObject {
|
||||
let proto = NETunnelProviderProtocol()
|
||||
proto.providerBundleIdentifier = "com.bobyrev.pcaphobos.tunnel"
|
||||
proto.serverAddress = profile.targetHost ?? "Phobos"
|
||||
proto.providerConfiguration = ["conf": conf, "ruDirect": ruDirect, "transport": transportMode]
|
||||
proto.providerConfiguration = ["conf": conf, "ruDirect": ruDirect, "transport": transportMode, "wsURL": wsURL]
|
||||
m.protocolConfiguration = proto
|
||||
m.localizedDescription = "PCA Phobos"
|
||||
m.isEnabled = true
|
||||
|
||||
Reference in New Issue
Block a user