Add a relay ping and backend status to the iOS app
The relay now answers a stateless ping with pong. The Relay card auto-checks on appear and address changes, showing latency or the connection error; this verifies the endpoint speaks the share protocol, not just that a TCP port is open.
This commit is contained in:
@@ -21,12 +21,16 @@ final class AppModel: ObservableObject {
|
||||
@Published var errorMessage: String?
|
||||
@Published var notice: String?
|
||||
@Published var showAdvanced = false
|
||||
@Published var relayCheck: RelayCheck?
|
||||
@Published var isCheckingRelay = false
|
||||
|
||||
private let session: ShareSession = ShareNewSession()!
|
||||
private let receivedDir: URL
|
||||
private let seedsDir: URL
|
||||
private var pollTimer: Timer?
|
||||
private var noticeTask: Task<Void, Never>?
|
||||
private var relayCheckTask: Task<Void, Never>?
|
||||
private var relayCheckGeneration = 0
|
||||
|
||||
private enum Keys {
|
||||
static let relay = "share.relay"
|
||||
@@ -211,6 +215,63 @@ final class AppModel: ObservableObject {
|
||||
code = ShareGenerateCode()
|
||||
}
|
||||
|
||||
// MARK: - Relay reachability
|
||||
|
||||
/// Debounced automatic check used while the Relay card is visible. The
|
||||
/// short delay keeps every keystroke in the relay field from opening a
|
||||
/// socket.
|
||||
func scheduleRelayCheck() {
|
||||
relayCheckTask?.cancel()
|
||||
relayCheckGeneration += 1
|
||||
let generation = relayCheckGeneration
|
||||
relayCheck = nil
|
||||
|
||||
relayCheckTask = Task { [weak self] in
|
||||
try? await Task.sleep(nanoseconds: 350_000_000)
|
||||
guard !Task.isCancelled, let self else { return }
|
||||
await self.performRelayCheck(generation: generation)
|
||||
}
|
||||
}
|
||||
|
||||
/// Immediate check, used by the refresh button.
|
||||
func checkRelay() {
|
||||
relayCheckTask?.cancel()
|
||||
relayCheckGeneration += 1
|
||||
let generation = relayCheckGeneration
|
||||
relayCheckTask = Task { [weak self] in
|
||||
guard let self else { return }
|
||||
await self.performRelayCheck(generation: generation)
|
||||
}
|
||||
}
|
||||
|
||||
private func performRelayCheck(generation: Int) async {
|
||||
isCheckingRelay = true
|
||||
let relay = relay
|
||||
|
||||
// ShareCheckRelay performs a dial plus the protocol ping/pong. Keep
|
||||
// that off the main thread; the Go function has its own 5s timeouts.
|
||||
let json = await withCheckedContinuation { (continuation: CheckedContinuation<String, Never>) in
|
||||
DispatchQueue.global(qos: .utility).async {
|
||||
continuation.resume(returning: ShareCheckRelay(relay))
|
||||
}
|
||||
}
|
||||
|
||||
guard generation == relayCheckGeneration else { return }
|
||||
isCheckingRelay = false
|
||||
|
||||
guard let data = json.data(using: .utf8),
|
||||
let decoded = try? JSONDecoder().decode(RelayCheck.self, from: data) else {
|
||||
relayCheck = RelayCheck(
|
||||
ok: false,
|
||||
relay: relay.isEmpty ? ShareRelayDefault() : relay,
|
||||
latencyMs: 0,
|
||||
error: "Could not read the relay status."
|
||||
)
|
||||
return
|
||||
}
|
||||
relayCheck = decoded
|
||||
}
|
||||
|
||||
// MARK: - Status polling
|
||||
|
||||
private func startPolling() {
|
||||
|
||||
Reference in New Issue
Block a user