mirror of
https://github.com/shadowsocks/ShadowsocksX-NG.git
synced 2026-11-14 02:04:13 +00:00
Implements issue #71 with ADR-0020 (supersedes ADR-0019's endpoint-signature cleanup, startup cleanup, and health-gate cleanup). - New ShadowsocksX-NG2SystemProxyHelper target: app-bundled root LaunchDaemon (SMAppService.daemon, on-demand via MachServices) that mechanically applies a closed typed configuration or executes an unconditional clear. Clients are accepted by code-signing bundle identifier only; requests run serially on a shared queue (last-writer-wins). - SystemProxyConfiguration is now the closed typed apply payload (Codable over JSON XPC): explicit SOCKS/HTTP/HTTPS enable + endpoints, PAC and auto-discovery disabled with URL/JS removed, explicit simple-hostname exclusion, complete ExceptionsList. Unmodeled Proxies keys are preserved and equivalent values skip the commit. - Cleanup is a side effect of a persisted system-proxy intent on-to-off transition only: the switch itself, or the agent-off cascade (persist both intents off, clear, then stop listeners; failed cascade persistence records no transition and clears nothing). Launch with the switch off, health-gate closure, invalid targets, and passive events never clear. Failed clears are surfaced without deferred retry. - Endpoint-signature store, ownership migration, and direct Authorization Services writes are removed; old signature files stay inert. Helper unavailability or pending approval keeps an enabled intent pending and surfaces the Login Items approval path (HomeView button + menu label).
75 lines
2.4 KiB
Swift
75 lines
2.4 KiB
Swift
import Foundation
|
||
|
||
/// 特权 helper 的请求引擎(issue #71):解码 closed typed 请求 → 注入的特权
|
||
/// 写入器 → 编码应答。引擎本身不含任何产品策略,只在 payload 无效时拒绝。
|
||
/// 所有连接共享一条串行队列:请求逐个执行,最后接受的请求决定设备级配置
|
||
/// (last-writer-wins)。
|
||
final class SystemProxyHelperEngine: NSObject, SystemProxyHelperControlling {
|
||
/// 注入的特权写入器:真正触到 SystemConfiguration 的唯一边界。
|
||
typealias Performing =
|
||
@Sendable (SystemProxyHelperEngine.Request) throws ->
|
||
SystemProxyHelperEngine.Outcome
|
||
|
||
enum Request: Equatable, Sendable {
|
||
case apply(SystemProxyConfiguration)
|
||
case clear
|
||
}
|
||
|
||
enum Outcome: Equatable, Sendable {
|
||
case applied(SystemProxyWriteOutcome)
|
||
case cleared
|
||
}
|
||
|
||
/// 跨全部 XPC 连接共享的串行执行队列。
|
||
private static let sharedQueue = DispatchQueue(
|
||
label: "\(SystemProxyHelperIdentity.machServiceName).work")
|
||
|
||
private let queue: DispatchQueue
|
||
private let perform: Performing
|
||
|
||
init(perform: @escaping Performing, queue: DispatchQueue? = nil) {
|
||
self.perform = perform
|
||
self.queue = queue ?? Self.sharedQueue
|
||
super.init()
|
||
}
|
||
|
||
func apply(_ payload: Data, withReply reply: @escaping (Data) -> Void) {
|
||
queue.async { [perform] in
|
||
let request = Result { try SystemProxyHelperWire.decodeConfiguration(payload) }
|
||
reply(Self.responseData(request: request.map { .apply($0) }, perform: perform))
|
||
}
|
||
}
|
||
|
||
func clear(withReply reply: @escaping (Data) -> Void) {
|
||
queue.async { [perform] in
|
||
reply(Self.responseData(request: .success(.clear), perform: perform))
|
||
}
|
||
}
|
||
|
||
// MARK: - 请求执行(纯逻辑,可测)
|
||
|
||
static func responseData(
|
||
request: Result<Request, Error>, perform: Performing
|
||
) -> Data {
|
||
SystemProxyHelperWire.encode(response(request: request, perform: perform))
|
||
}
|
||
|
||
static func response(
|
||
request: Result<Request, Error>, perform: Performing
|
||
) -> SystemProxyHelperResponse {
|
||
do {
|
||
let outcome = try perform(request.get())
|
||
switch outcome {
|
||
case .applied(let writeOutcome):
|
||
return .applied(writeOutcome)
|
||
case .cleared:
|
||
return .cleared
|
||
}
|
||
} catch let error as SystemProxyError {
|
||
return .failure(error)
|
||
} catch {
|
||
return .failure(.helperUnavailable(String(describing: error)))
|
||
}
|
||
}
|
||
}
|