- InstanceEndpoint 取代 Instance.origin(旧数据 init(from:) 读成 .direct, 写盘只输出 endpoint;dedupKey 直通用 origin、中继用 relay:<nodeID>) - RelayWire:Swift 侧重算 contracts/test-vectors.json(CanonicalJSON 手写解析器, 因为 Foundation 的解码器会抹掉'拼写',而 intent 摘要定义的正是拼写) - NodeTransport 抽象 + DirectTransport / RelayTransport / NodeTransportRouter; DirectTransport 是薄适配,直通路径行为不变 - RelayTransport:信封构造、签名、加密、验签、解密;响应验签失败即丢弃, 四类以上失败各有独立 UpstreamError 分支与用户文案 - DeviceIdentity(沙盒文件 0600,配对时由节点分配 deviceID)、RelayPairing、配对 sheet - 卡片通道徽标 + freshness 副标题;危险写 intent 在发送瞬间生成 - scripts/export-app-routes.swift 生成 vendored 路由表,AppRoutesContractTests 反向守卫 - 测试 78→92:向量互算、迁移(字面量旧 JSON)、配对四类失败、路由器、 传输往返与各类拒绝路径
42 lines
1.4 KiB
Swift
42 lines
1.4 KiB
Swift
import Foundation
|
|
|
|
@MainActor
|
|
final class DeviceViewModel: ObservableObject {
|
|
@Published var snapshot: DeviceSnapshot?
|
|
@Published var isLoading = false
|
|
@Published var errorMessage: String?
|
|
@Published private(set) var connectionStatus: ConnectionStatus = .unknown
|
|
|
|
let instance: Instance
|
|
private let service: DeviceService
|
|
private let probe: ConnectionProbe?
|
|
|
|
init(instance: Instance, service: DeviceService, probe: ConnectionProbe? = nil) {
|
|
self.instance = instance
|
|
self.service = service
|
|
self.probe = probe
|
|
}
|
|
|
|
func refresh() async {
|
|
isLoading = true
|
|
errorMessage = nil
|
|
do {
|
|
snapshot = try await service.snapshot(instance)
|
|
} catch DeviceServiceError.unauthorized, UpstreamError.unauthorized {
|
|
errorMessage = "需要实例密码登录后才能读取设备数据。"
|
|
} catch let error as UpstreamError {
|
|
errorMessage = ErrorMessage.describe(error, nodeName: instance.name)
|
|
} catch {
|
|
errorMessage = "节点无响应,请确认地址可访问、设备已开机且端口未被拦截。"
|
|
}
|
|
guard let probe else {
|
|
connectionStatus = .offline
|
|
isLoading = false
|
|
return
|
|
}
|
|
let result = try? await probe.test(instance)
|
|
connectionStatus = result?.status ?? .offline
|
|
isLoading = false
|
|
}
|
|
}
|