Add camera QR pairing, Bonjour discovery and pinned address recovery

This commit is contained in:
2026-09-12 15:52:31 +03:00
parent 5f16ec48aa
commit 397caf23ce
12 changed files with 451 additions and 20 deletions
+25 -2
View File
@@ -10,6 +10,8 @@ final class PlayerStore: ObservableObject {
@Published var connected = false
@Published var busy = false
@Published var pairing = false
@Published var incomingInvitation: String?
let discovery = DiscoveryStore()
private(set) var receivedAt = Date()
private var client: BridgeClient?
private var runner: Task<Void, Never>?
@@ -43,7 +45,16 @@ final class PlayerStore: ObservableObject {
func setActive(_ active: Bool) {
self.active = active
if active { resume() } else { stop(); status = "Соединение приостановлено" }
if active { discovery.start(); resume() }
else { discovery.stop(); stop(); status = "Соединение приостановлено" }
}
func importInvitation(_ text: String) {
guard connection == nil, !pairing else {
error = "Для подключения другого ПК сначала выберите «Забыть компьютер»."
return
}
incomingInvitation = text
}
func resume() {
@@ -52,15 +63,26 @@ final class PlayerStore: ObservableObject {
let current = generation
runner = Task { [weak self] in
var delay: UInt64 = 1
var attempt = 0
while !Task.isCancelled {
guard let self, self.generation == current else { return }
let client = BridgeClient(connection)
let saved = self.connection ?? connection
let alternatives = self.discovery.computers.compactMap { $0.reconnect(saved) }
.filter { $0.endpoint != saved.endpoint }
let candidates = [saved] + alternatives
let candidate = candidates[attempt % candidates.count]
let client = BridgeClient(candidate)
self.client = client
self.status = "Подключение к ПК…"
do {
let initial = try await client.state()
try Task.checkCancellation()
guard self.generation == current else { client.close(); return }
// Persist a discovered address only after pinned, authenticated HTTPS succeeds.
if candidate.endpoint != saved.endpoint {
try KeychainStore.save(candidate)
self.connection = candidate
}
self.accept(initial, client: client)
client.openEvents()
// Abort a silent/stalled stream even if the OS has not detected a lost Wi-Fi link.
@@ -94,6 +116,7 @@ final class PlayerStore: ObservableObject {
}
self.status = "ПК недоступен. Повтор через \(delay) с"
self.error = error.localizedDescription
attempt += 1
}
do { try await Task.sleep(nanoseconds: delay * 1_000_000_000) } catch { return }
delay = min(delay * 2, 16)