mirror of
https://github.com/shadowsocks/ShadowsocksX-NG.git
synced 2026-11-12 22:45:08 +00:00
实现 #60(ACL 1/8):agent 意图与系统代理意图拆为两个独立开关, 首次运行默认 agent 开、系统代理关;显式关闭 agent 持久化并在 GUI 重启后生效;关闭系统代理只恢复 NG2 持有的系统设置,本地 SOCKS/HTTP 继续监听;关闭 agent 先恢复系统设置再停止监听。无活动目标时 agent 以空服务器列表契约提供本地监听(isWellFormed 放行空 servers,上游 sslocal v1.25.0 接受);需要代理出口的模式在活动目标缺失或端点不健 康时系统代理保持待应用,条件恢复后随下次收敛自动应用。首页、状态菜 单与侧栏状态卡经同一代理控制快照展示/操作 agent 意图/运行状态与系 统代理意图/实际应用及 typed 错误。测试覆盖启动顺序、持久化、健康门 禁、ownership 冲突、无效活动目标与 GUI 重同步(含真实 sslocal 空监 听回环证据);替身测试不表述为真实系统写入验证。
85 lines
2.9 KiB
Swift
85 lines
2.9 KiB
Swift
import Combine
|
||
import Foundation
|
||
|
||
/// 生产运行时 adapter(issue #47):现有 `ProxyRuntimeController` 的唯一
|
||
/// 包装,由应用组合根接线。只把控制器既有能力与观察投影到 workflow 缝上,
|
||
/// 不复制状态机、generation、健康门禁与系统代理 ownership 语义。
|
||
@MainActor
|
||
final class ControllerProxyRuntimeAdapter: ProxyRuntimeAdapting {
|
||
private let controller: ProxyRuntimeController
|
||
|
||
init(controller: ProxyRuntimeController) {
|
||
self.controller = controller
|
||
}
|
||
|
||
var runtimeFacts: ProxyRuntimeFacts { controller.runtimeFacts }
|
||
|
||
var agentIntentEnabled: Bool { controller.agentIntentEnabled }
|
||
|
||
var activationFailure: ActivationFailure? { controller.lastActivationFailure }
|
||
|
||
var systemProxyIntentEnabled: Bool { controller.systemProxyIntentEnabled }
|
||
|
||
var systemProxyApplication: SystemProxyApplicationFacts {
|
||
switch controller.systemProxyState {
|
||
case .idle: return .idle
|
||
case .pending: return .pending
|
||
case .applied: return .applied
|
||
case .failed(let facts): return .failed(facts)
|
||
}
|
||
}
|
||
|
||
var proxyMode: ProxyMode { controller.proxyMode }
|
||
|
||
var skippedInvalidServerCount: Int { controller.skippedServers.count }
|
||
|
||
var activeTargetID: NodeID? { controller.activeTargetID }
|
||
|
||
var httpExportCapability: HTTPExportCapability {
|
||
HTTPExportCapability(listen: controller.listenSettings)
|
||
}
|
||
|
||
/// 控制器的 `objectWillChange` 是 willChange 语义;主队列 hop 落地时被
|
||
/// 发布的新值已可读,workflow 重观察不会读到半程状态。
|
||
var changes: AnyPublisher<Void, Never> {
|
||
controller.objectWillChange
|
||
.map { _ in () }
|
||
.receive(on: DispatchQueue.main)
|
||
.eraseToAnyPublisher()
|
||
}
|
||
|
||
func resyncOnLaunch() async {
|
||
await controller.resyncOnLaunch()
|
||
}
|
||
|
||
func setAgentEnabled(_ enabled: Bool) async {
|
||
await controller.setAgentEnabled(enabled)
|
||
}
|
||
|
||
func setSystemProxyEnabled(_ enabled: Bool) async {
|
||
await controller.setSystemProxyEnabled(enabled)
|
||
}
|
||
|
||
func setProxyMode(_ mode: ProxyMode) async {
|
||
await controller.setProxyMode(mode)
|
||
}
|
||
}
|
||
|
||
/// HTTP 导出能力的唯一派生点:监听设置 → 可复制导出行。地址取监听范围的
|
||
/// 对外地址(回环态 127.0.0.1,主机态为对外公布地址),与 PAC 语义一致。
|
||
extension HTTPExportCapability {
|
||
init(listen: SslocalListenSettings) {
|
||
let endpoint = "http://\(listen.scope.advertisedAddress):\(listen.httpPort)"
|
||
self.init(copyableLine: "export http_proxy=\(endpoint);export https_proxy=\(endpoint);")
|
||
}
|
||
}
|
||
|
||
/// 目录侧唯一的目标事实出口(窄缝,story 28):存在性 + 显示名路径摘要;
|
||
/// 完整目录树、订阅对象与凭据不出此接口。
|
||
extension CatalogWorkflow: ProxyTargetFactsReading {
|
||
func activeTargetFacts(for targetID: NodeID?) -> ProxyActiveTargetFacts? {
|
||
guard let targetID else { return nil }
|
||
return ProxyActiveTargetFacts(id: targetID, pathSummary: tree.pathSummary(for: targetID))
|
||
}
|
||
}
|