- 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)、配对四类失败、路由器、 传输往返与各类拒绝路径
64 lines
3.7 KiB
Swift
64 lines
3.7 KiB
Swift
import Foundation
|
|
|
|
/// User-facing text for every failure the app can produce.
|
|
///
|
|
/// The relay work is what forced this out of `ConnectionProbe`: with one hop there was one thing
|
|
/// that could go wrong, and "请求失败" was an acceptable summary. With two hops there are four
|
|
/// distinct situations that call for four different actions, and a message that cannot tell them
|
|
/// apart is the difference between a five-second fix and a support thread.
|
|
public enum ErrorMessage {
|
|
public static let network = "暂时无法连接实例,请稍后重试。"
|
|
public static let unauthorized = "实例登录状态已失效,请重新登录。"
|
|
|
|
// MARK: - Relay mode
|
|
|
|
/// Describes a relay-mode failure in terms of what the user should do next.
|
|
public static func describe(_ error: UpstreamError, nodeName: String) -> String {
|
|
switch error {
|
|
case .relayUnreachable:
|
|
return "无法连接中继服务器。请检查网络,或确认中继是否在运行。"
|
|
case .relayTimeout:
|
|
return "中继响应超时。稍后重试;如果反复出现,请检查中继负载。"
|
|
case .relayRejectedDevice:
|
|
return "中继已不再接受这台设备的凭据,需要在节点上重新配对。"
|
|
case .relayNodeUnknown:
|
|
return "中继不认识节点 \(nodeName)。可能节点已离线并被移出,或这台手机未与该节点配对。"
|
|
case .agentOffline:
|
|
return "节点 \(nodeName) 的 agent 当前未连接到中继。等它上线后重试。"
|
|
case .nodeBusy:
|
|
return "节点 \(nodeName) 正在处理其它请求,稍后重试。"
|
|
case .agentTimeout:
|
|
// The one message that must not invite a blind retry: the node may have executed it.
|
|
return "节点 \(nodeName) 未在时限内应答。该操作可能已经执行,请先确认结果再决定是否重试。"
|
|
case .relayBadEnvelope:
|
|
return "中继拒绝了发往节点 \(nodeName) 的请求信封。这通常意味着两端版本不一致。"
|
|
case .relayRateLimited:
|
|
return "对节点 \(nodeName) 的请求过于频繁,已被中继限流。稍后再试。"
|
|
case .relayPolicyDenied:
|
|
return "节点 \(nodeName) 的 agent 不允许该操作。该路径不在它的授权范围内。"
|
|
case .confirmationRejected:
|
|
return "节点 \(nodeName) 拒绝了这次确认:确认信息与请求不匹配,操作未执行。请重新发起。"
|
|
case .envelopeExpired:
|
|
return "对节点 \(nodeName) 的请求已过期或重复。请重新发起;如果反复出现,请检查手机与节点的时钟。"
|
|
case .nodeRateLimited:
|
|
return "节点 \(nodeName) 限制了危险写操作频率,稍后再试。"
|
|
case .nodeAuthFailed:
|
|
return "节点 \(nodeName) 的 agent 无法登录本机 SimAdmin。需要在节点上修正 agent 的密码配置。"
|
|
case .payloadTooLarge:
|
|
return "请求内容超过节点 \(nodeName) 允许的大小。"
|
|
case .relayResponseUnverifiable:
|
|
return "节点 \(nodeName) 的应答未通过签名校验,已丢弃。这可能是中继被篡改或密钥不匹配。"
|
|
case .relayResponseMismatch:
|
|
return "中继返回了与请求不匹配的应答,已丢弃。"
|
|
case .envelopeRejected:
|
|
return "节点 \(nodeName) 拒绝了该请求的签名或格式。可能需要在节点上重新配对。"
|
|
case .unauthorized:
|
|
return unauthorized
|
|
case .timeout, .transportFailed:
|
|
return network
|
|
default:
|
|
return "请求失败:\(error)"
|
|
}
|
|
}
|
|
}
|