feat(ios): 中继通道 P4/P5——端点抽象、信封、传输、配对与分层错误
- 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)、配对四类失败、路由器、 传输往返与各类拒绝路径
This commit is contained in:
@@ -29,6 +29,19 @@ final class FleetViewModel: ObservableObject {
|
||||
var uptimeSeconds: Double?
|
||||
var chainValues: DeviceLinkState?
|
||||
var testing: Bool
|
||||
/// Why the last probe ended where it did, for the detail text.
|
||||
var probeError: UpstreamError?
|
||||
/// When the last probe finished. The list shows how old a reading is, which in relay mode is
|
||||
/// the difference between "online" meaning now and meaning forty seconds ago.
|
||||
var checkedAt: Date?
|
||||
|
||||
/// Which path this node is reached by. The badge exists because without it "this one is
|
||||
/// slow" has no answer that a user can act on.
|
||||
var channel: InstanceChannel { instance.endpoint.channel }
|
||||
|
||||
var freshness: SnapshotFreshness {
|
||||
testing ? .unknown : SnapshotFreshness.classify(age: checkedAt.map { Date().timeIntervalSince($0) })
|
||||
}
|
||||
|
||||
var storageUsageRatio: Double? {
|
||||
guard let total = totalStorageBytes, let available = availableStorageBytes, total > 0 else {
|
||||
@@ -143,7 +156,7 @@ final class FleetViewModel: ObservableObject {
|
||||
expectedRevision: original.instance.configRevision,
|
||||
InstancePatch(
|
||||
name: input.name,
|
||||
origin: input.origin,
|
||||
endpoint: input.endpoint,
|
||||
tags: input.tags,
|
||||
password: input.password
|
||||
)
|
||||
@@ -215,6 +228,8 @@ final class FleetViewModel: ObservableObject {
|
||||
) {
|
||||
guard let index = rows.firstIndex(where: { $0.id == id }) else { return }
|
||||
rows[index].status = result?.status ?? .unknown
|
||||
rows[index].probeError = result?.error
|
||||
rows[index].checkedAt = result?.checkedAt
|
||||
if let summary {
|
||||
rows[index].deviceName = summary.deviceName
|
||||
rows[index].phoneNumber = summary.phoneNumber
|
||||
@@ -260,7 +275,7 @@ final class FleetViewModel: ObservableObject {
|
||||
guard !searchText.isEmpty else { return true }
|
||||
let haystacks: [String] = [
|
||||
row.instance.name,
|
||||
row.instance.origin.absoluteString,
|
||||
row.instance.endpoint.displayAddress,
|
||||
row.instance.tags.joined(separator: " "),
|
||||
row.operatorName ?? "",
|
||||
row.phoneNumber ?? "",
|
||||
|
||||
Reference in New Issue
Block a user