Browse Source

fix(mac): separate passive state sync from active voice dictation and force replace

main
Ali Alavi 19 hours ago
parent
commit
0effbaadcf
  1. 45
      mac/src/RelayClient.swift

45
mac/src/RelayClient.swift

@ -17,8 +17,13 @@ public final class RelayClient: NSObject, URLSessionWebSocketDelegate {
private var monitorTimerSource: DispatchSourceTimer?
private let monitorQueue = DispatchQueue(label: "com.soniox.macsync.monitor", qos: .userInteractive)
// Callback: (text, cursor, isFullReplace)
public var onRemoteUpdateReceived: ((String, Int?, Bool) -> Void)?
// Dedup guard: prevent rapid duplicate keystrokes within 200ms
private var lastInjectedText: String = ""
private var lastInjectedTime: Double = 0
override private init() {
super.init()
let config = URLSessionConfiguration.default
@ -97,7 +102,7 @@ public final class RelayClient: NSObject, URLSessionWebSocketDelegate {
return
}
let action = json["action"] as? String ?? json["type"] as? String
let action = json["action"] as? String ?? json["type"] as? String ?? ""
let source = json["source"] as? String ?? ""
// Ignore echo messages originated by Mac itself
@ -106,12 +111,42 @@ public final class RelayClient: NSObject, URLSessionWebSocketDelegate {
let updateText = json["text"] as? String ?? json["insert_text"] as? String ?? ""
let cursor = json["cursor"] as? Int ?? json["cursor_pos"] as? Int
if action == "paste" || action == "set_text" || action == "update_input" || action == "phone_input_edit" || action == "sync_state" || action == "insert_speech" {
let isFullReplace = (action == "update_input" || action == "set_text" || action == "force_replace")
print("RelayClient: ⚡ Received remote update: '\(updateText.prefix(30))...' (replace: \(isFullReplace), cursor: \(cursor ?? -1))")
let now = Date().timeIntervalSince1970
// 1. Live Voice Dictation from Phone Mic: Paste ONLY new speech at active cursor (Cmd+V)
if action == "insert_speech" || action == "speech_insert" {
guard !updateText.isEmpty else { return }
// Dedup exact duplicate within 250ms
if updateText == lastInjectedText && (now - lastInjectedTime) < 0.25 {
return
}
lastInjectedText = updateText
lastInjectedTime = now
print("RelayClient: 🎙️ Voice Dictation Paste: '\(updateText.prefix(30))...' (Cmd+V at cursor)")
DispatchQueue.main.async {
self.onRemoteUpdateReceived?(updateText, cursor, false)
}
}
// 2. Explicit "Send to Mac" Button on Phone: Full Replace (Cmd+A + Cmd+V)
else if action == "update_input" || action == "force_replace" || action == "paste" {
guard !updateText.isEmpty else { return }
lastInjectedText = updateText
lastInjectedTime = now
print("RelayClient: 🚀 Force Replace All: '\(updateText.prefix(30))...' (Cmd+A + Cmd+V)")
DispatchQueue.main.async {
self.onRemoteUpdateReceived?(updateText, cursor, isFullReplace)
self.onRemoteUpdateReceived?(updateText, cursor, true)
}
}
// 3. Passive State Synchronization (typing on phone keyboard):
// Updates room state cache only DOES NOT trigger keystrokes on Mac!
else if action == "sync_state" || action == "phone_input_edit" {
// Passive sync only
}
}

Loading…
Cancel
Save