chore: sync local changes
This commit is contained in:
@@ -25,7 +25,6 @@ final class BridgeConnectionController {
|
||||
private let discovery = BridgeDiscoveryModel()
|
||||
private weak var appModel: NodeAppModel?
|
||||
private var didAutoConnect = false
|
||||
private var seenStableIDs = Set<String>()
|
||||
|
||||
private let bridgeClientFactory: @Sendable () -> any BridgePairingClient
|
||||
|
||||
@@ -120,9 +119,15 @@ final class BridgeConnectionController {
|
||||
return
|
||||
}
|
||||
|
||||
let targetStableID = defaults.string(forKey: "bridge.lastDiscoveredStableID")?
|
||||
let preferredStableID = defaults.string(forKey: "bridge.preferredStableID")?
|
||||
.trimmingCharacters(in: .whitespacesAndNewlines) ?? ""
|
||||
guard !targetStableID.isEmpty else { return }
|
||||
let lastDiscoveredStableID = defaults.string(forKey: "bridge.lastDiscoveredStableID")?
|
||||
.trimmingCharacters(in: .whitespacesAndNewlines) ?? ""
|
||||
|
||||
let candidates = [preferredStableID, lastDiscoveredStableID].filter { !$0.isEmpty }
|
||||
guard let targetStableID = candidates.first(where: { id in
|
||||
self.bridges.contains(where: { $0.stableID == id })
|
||||
}) else { return }
|
||||
|
||||
guard let target = self.bridges.first(where: { $0.stableID == targetStableID }) else { return }
|
||||
|
||||
@@ -131,11 +136,18 @@ final class BridgeConnectionController {
|
||||
}
|
||||
|
||||
private func updateLastDiscoveredBridge(from bridges: [BridgeDiscoveryModel.DiscoveredBridge]) {
|
||||
let newlyDiscovered = bridges.filter { self.seenStableIDs.insert($0.stableID).inserted }
|
||||
guard let last = newlyDiscovered.last else { return }
|
||||
let defaults = UserDefaults.standard
|
||||
let preferred = defaults.string(forKey: "bridge.preferredStableID")?
|
||||
.trimmingCharacters(in: .whitespacesAndNewlines) ?? ""
|
||||
let existingLast = defaults.string(forKey: "bridge.lastDiscoveredStableID")?
|
||||
.trimmingCharacters(in: .whitespacesAndNewlines) ?? ""
|
||||
|
||||
UserDefaults.standard.set(last.stableID, forKey: "bridge.lastDiscoveredStableID")
|
||||
BridgeSettingsStore.saveLastDiscoveredBridgeStableID(last.stableID)
|
||||
// Avoid overriding user intent (preferred/lastDiscovered are also set on manual Connect).
|
||||
guard preferred.isEmpty, existingLast.isEmpty else { return }
|
||||
guard let first = bridges.first else { return }
|
||||
|
||||
defaults.set(first.stableID, forKey: "bridge.lastDiscoveredStableID")
|
||||
BridgeSettingsStore.saveLastDiscoveredBridgeStableID(first.stableID)
|
||||
}
|
||||
|
||||
private func makeHello(token: String) -> BridgeHello {
|
||||
|
||||
@@ -461,11 +461,13 @@ final class TalkModeManager: NSObject {
|
||||
let canUseElevenLabs = (voiceId?.isEmpty == false) && (apiKey?.isEmpty == false)
|
||||
|
||||
if canUseElevenLabs, let voiceId, let apiKey {
|
||||
let desiredOutputFormat = directive?.outputFormat ?? self.defaultOutputFormat ?? "pcm_44100"
|
||||
let outputFormat = ElevenLabsTTSClient.validatedOutputFormat(desiredOutputFormat)
|
||||
if outputFormat == nil, let desiredOutputFormat, !desiredOutputFormat.isEmpty {
|
||||
let desiredOutputFormat = (directive?.outputFormat ?? self.defaultOutputFormat)?
|
||||
.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
let requestedOutputFormat = (desiredOutputFormat?.isEmpty == false) ? desiredOutputFormat : nil
|
||||
let outputFormat = ElevenLabsTTSClient.validatedOutputFormat(requestedOutputFormat ?? "pcm_44100")
|
||||
if outputFormat == nil, let requestedOutputFormat {
|
||||
self.logger.warning(
|
||||
"talk output_format unsupported for local playback: \(desiredOutputFormat, privacy: .public)")
|
||||
"talk output_format unsupported for local playback: \(requestedOutputFormat, privacy: .public)")
|
||||
}
|
||||
|
||||
let modelId = directive?.modelId ?? self.currentModelId ?? self.defaultModelId
|
||||
|
||||
@@ -60,6 +60,35 @@ private func withUserDefaults<T>(_ updates: [String: Any?], _ body: () throws ->
|
||||
return try body()
|
||||
}
|
||||
|
||||
@MainActor
|
||||
private func withUserDefaults<T>(
|
||||
_ updates: [String: Any?],
|
||||
_ body: () async throws -> T) async rethrows -> T
|
||||
{
|
||||
let defaults = UserDefaults.standard
|
||||
var snapshot: [String: Any?] = [:]
|
||||
for key in updates.keys {
|
||||
snapshot[key] = defaults.object(forKey: key)
|
||||
}
|
||||
for (key, value) in updates {
|
||||
if let value {
|
||||
defaults.set(value, forKey: key)
|
||||
} else {
|
||||
defaults.removeObject(forKey: key)
|
||||
}
|
||||
}
|
||||
defer {
|
||||
for (key, value) in snapshot {
|
||||
if let value {
|
||||
defaults.set(value, forKey: key)
|
||||
} else {
|
||||
defaults.removeObject(forKey: key)
|
||||
}
|
||||
}
|
||||
}
|
||||
return try await body()
|
||||
}
|
||||
|
||||
private func withKeychainValues<T>(_ updates: [KeychainEntry: String?], _ body: () throws -> T) rethrows -> T {
|
||||
var snapshot: [KeychainEntry: String?] = [:]
|
||||
for entry in updates.keys {
|
||||
@@ -84,6 +113,34 @@ private func withKeychainValues<T>(_ updates: [KeychainEntry: String?], _ body:
|
||||
return try body()
|
||||
}
|
||||
|
||||
@MainActor
|
||||
private func withKeychainValues<T>(
|
||||
_ updates: [KeychainEntry: String?],
|
||||
_ body: () async throws -> T) async rethrows -> T
|
||||
{
|
||||
var snapshot: [KeychainEntry: String?] = [:]
|
||||
for entry in updates.keys {
|
||||
snapshot[entry] = KeychainStore.loadString(service: entry.service, account: entry.account)
|
||||
}
|
||||
for (entry, value) in updates {
|
||||
if let value {
|
||||
_ = KeychainStore.saveString(value, service: entry.service, account: entry.account)
|
||||
} else {
|
||||
_ = KeychainStore.delete(service: entry.service, account: entry.account)
|
||||
}
|
||||
}
|
||||
defer {
|
||||
for (entry, value) in snapshot {
|
||||
if let value {
|
||||
_ = KeychainStore.saveString(value, service: entry.service, account: entry.account)
|
||||
} else {
|
||||
_ = KeychainStore.delete(service: entry.service, account: entry.account)
|
||||
}
|
||||
}
|
||||
}
|
||||
return try await body()
|
||||
}
|
||||
|
||||
@Suite(.serialized) struct BridgeConnectionControllerTests {
|
||||
@Test @MainActor func resolvedDisplayNameSetsDefaultWhenMissing() {
|
||||
let defaults = UserDefaults.standard
|
||||
@@ -192,13 +249,13 @@ private func withKeychainValues<T>(_ updates: [KeychainEntry: String?], _ body:
|
||||
let mock = MockBridgePairingClient(resultToken: "new-token")
|
||||
let account = "bridge-token.ios-test"
|
||||
|
||||
withKeychainValues([
|
||||
await withKeychainValues([
|
||||
instanceIdEntry: nil,
|
||||
preferredBridgeEntry: nil,
|
||||
lastBridgeEntry: nil,
|
||||
KeychainEntry(service: bridgeService, account: account): "old-token",
|
||||
]) {
|
||||
withUserDefaults([
|
||||
await withUserDefaults([
|
||||
"node.instanceId": "ios-test",
|
||||
"bridge.lastDiscoveredStableID": "bridge-1",
|
||||
"bridge.manual.enabled": false,
|
||||
@@ -224,4 +281,61 @@ private func withKeychainValues<T>(_ updates: [KeychainEntry: String?], _ body:
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test @MainActor func autoConnectPrefersPreferredBridgeOverLastDiscovered() async {
|
||||
let bridgeA = BridgeDiscoveryModel.DiscoveredBridge(
|
||||
name: "Gateway A",
|
||||
endpoint: .hostPort(host: NWEndpoint.Host("127.0.0.1"), port: 18790),
|
||||
stableID: "bridge-1",
|
||||
debugID: "bridge-a",
|
||||
lanHost: "MacA.local",
|
||||
tailnetDns: nil,
|
||||
gatewayPort: 18789,
|
||||
bridgePort: 18790,
|
||||
canvasPort: 18793,
|
||||
cliPath: nil)
|
||||
let bridgeB = BridgeDiscoveryModel.DiscoveredBridge(
|
||||
name: "Gateway B",
|
||||
endpoint: .hostPort(host: NWEndpoint.Host("127.0.0.1"), port: 28790),
|
||||
stableID: "bridge-2",
|
||||
debugID: "bridge-b",
|
||||
lanHost: "MacB.local",
|
||||
tailnetDns: nil,
|
||||
gatewayPort: 28789,
|
||||
bridgePort: 28790,
|
||||
canvasPort: 28793,
|
||||
cliPath: nil)
|
||||
|
||||
let mock = MockBridgePairingClient(resultToken: "token-ok")
|
||||
let account = "bridge-token.ios-test"
|
||||
|
||||
await withKeychainValues([
|
||||
instanceIdEntry: nil,
|
||||
preferredBridgeEntry: nil,
|
||||
lastBridgeEntry: nil,
|
||||
KeychainEntry(service: bridgeService, account: account): "old-token",
|
||||
]) {
|
||||
await withUserDefaults([
|
||||
"node.instanceId": "ios-test",
|
||||
"bridge.preferredStableID": "bridge-2",
|
||||
"bridge.lastDiscoveredStableID": "bridge-1",
|
||||
"bridge.manual.enabled": false,
|
||||
]) {
|
||||
let appModel = NodeAppModel()
|
||||
let controller = BridgeConnectionController(
|
||||
appModel: appModel,
|
||||
startDiscovery: false,
|
||||
bridgeClientFactory: { mock })
|
||||
controller._test_setBridges([bridgeA, bridgeB])
|
||||
controller._test_triggerAutoConnect()
|
||||
|
||||
for _ in 0..<20 {
|
||||
if appModel.connectedBridgeID == bridgeB.stableID { break }
|
||||
try? await Task.sleep(nanoseconds: 50_000_000)
|
||||
}
|
||||
|
||||
#expect(appModel.connectedBridgeID == bridgeB.stableID)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user