fix: escalate an interactive connect to a bond refresh when a bonded device is in range but refuses to open - #103
Merged
Conversation
…device is in range but refuses to open A Magic device can sit at paired=true locally while it actually answers to the other Mac (a handoff done outside the app, or desynced state). An interactive no-peer take then hits the bonded branch, fails openConnection(), and reports 'Couldn't Connect' with no path that ever breaks the dead bond - the user is stuck until they unpair manually. Now, when the plain open fails and the RSSI probe can still see the device - alive and in range, yet refusing the bonded connect - the attempt removes the stale record and falls through to a fresh pair, the same refresh the peer-takeover path already performs. The probe gate keeps a healthy bond safe: a device that is merely off or out of range doesn't answer the probe, and its bond (and macOS's automatic reconnect) is left intact, failing exactly as before. Only the interactive entry point opts in; the background watcher and reclaim paths keep retrying the plain open so a transient link failure in a retry loop can't repeatedly tear bonds down. The remove-and-settle block is extracted into removeStaleBond, shared with the existing refreshPairingBeforeConnect path.
|
🎉 This PR is included in version 2.25.5 🎉 The release is available on GitHub release Your semantic-release bot 📦🚀 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
A Magic device can sit at
paired=truelocally while it actually answers to the other Mac — a handoff done outside the app, or desynced state. An interactive no-peer take (dropdown click with the peer unreachable,takePeripheralFromPeer's fallback, the take-all fallback) then hits the bonded branch inconnectPeripheral, failsopenConnection(), and reports "Couldn't Connect" — with no path that ever breaks the dead bond. The user is stuck until they unpair manually in System Settings.This ports the useful half of xneg's 22ed2d3, which routes no-peer takes through the pairing-refresh path unconditionally. Unconditional refresh would regress the common case though: clicking a switched-off peripheral that's healthily bonded to this Mac would
-removea good bond, fail the re-pair against an absent device, and cost the automatic reconnect macOS performs when the device comes back.Fix
Escalate instead of always refreshing. When the plain open fails and the RSSI probe can still see the device — alive and in range, yet refusing the bonded connect, which is the stale-bond signature — the attempt removes the stale record and falls through to a fresh pair, the same refresh the peer-takeover path (
refreshPairingBeforeConnect) already performs. If the probe can't see the device (merely off / out of range), it fails exactly as before and the bond is left intact.Only the interactive entry point (
connectPeripheral(_:)) opts in. The background watcher and reclaim paths keep retrying the plain open, so a transient link failure inside a retry loop can't repeatedly tear bonds down.The remove-and-settle block is extracted into
removeStaleBond(of:id:name:), shared between the existing refresh path and the new escalation.Testing
swiftc -typecheckpasses (this machine has no Xcode.app, so noxcodebuildrun; the only pre-existing failure under CLT is the#Previewmacro plugin).