nvrsion: Add fix for QR Code Scan Failure in AppStore, Sync, and Tests; explore connection failures over local network; update Info.plist, Models, and SettingsView.
Nucleic-Promote: 1 Co-authored-by: Nucleic <[email protected]>
This commit is contained in:
@@ -6,6 +6,13 @@
|
||||
<array>
|
||||
<string>_nucleic._tcp</string>
|
||||
</array>
|
||||
<!-- iOS 14+ gates ALL local-network access — both Bonjour discovery (_nucleic._tcp above)
|
||||
and a direct NWConnection to the Mac's LAN IP from the pairing QR — behind the
|
||||
local-network permission, which only prompts if this usage string is present. Without
|
||||
it the grant never happens and every LAN connection fails silently, so pairing and
|
||||
reconnect appear to "do nothing." -->
|
||||
<key>NSLocalNetworkUsageDescription</key>
|
||||
<string>Nucleic connects to your Mac on the local network to pair and to sync your sessions and approvals.</string>
|
||||
<key>NucleicChannel</key>
|
||||
<string>$(NUCLEIC_CHANNEL)</string>
|
||||
<!-- The short git commit this build came from, shown in the non-release banner to match
|
||||
|
||||
@@ -695,7 +695,35 @@ final class RemoteStore: ObservableObject {
|
||||
}
|
||||
|
||||
private func makeChannel(_ endpoint: NWEndpoint) -> NWFrameChannel {
|
||||
NWFrameChannel(endpoint: endpoint)
|
||||
let channel = NWFrameChannel(endpoint: endpoint)
|
||||
// Surface a transport-level failure with an actionable message. Without this, a refused
|
||||
// connection or a denied local-network permission finished the frame stream and only
|
||||
// showed up as a generic "handshake closed" — hiding the real (usually fixable) cause.
|
||||
channel.onFailed = { [weak self] error in
|
||||
Task { @MainActor in self?.handleTransportFailure(error) }
|
||||
}
|
||||
return channel
|
||||
}
|
||||
|
||||
/// Map an `NWConnection` failure to a connectivity state the pairing/onboarding UI can act on.
|
||||
/// Only meaningful while we're still establishing the link; once `.connected`, a drop is the
|
||||
/// normal `.closed` → reconnect path's job.
|
||||
private func handleTransportFailure(_ error: String) {
|
||||
guard connectivity != .connected else { return }
|
||||
connectivity = .failed(Self.friendlyTransportError(error))
|
||||
}
|
||||
|
||||
/// Turn a raw `NWError` string into a short, fixable hint. The common onboarding failures are
|
||||
/// a denied Local Network permission (EPERM / -65555) and the Mac not listening (refused).
|
||||
private static func friendlyTransportError(_ error: String) -> String {
|
||||
let lower = error.lowercased()
|
||||
if lower.contains("denied") || lower.contains("not permitted") || lower.contains("65555") {
|
||||
return "Can't reach the local network. In Settings ▸ Nucleic, allow Local Network access, then try again."
|
||||
}
|
||||
if lower.contains("refused") {
|
||||
return "Your Mac refused the connection. Check that remote access is still on in Nucleic ▸ Settings."
|
||||
}
|
||||
return "Couldn't connect to your Mac — make sure it's on the same Wi‑Fi and remote access is on."
|
||||
}
|
||||
|
||||
private func resolveEndpoint(fingerprint: String?, lanHost: String?, lanPort: UInt16?) -> NWEndpoint? {
|
||||
|
||||
@@ -126,6 +126,10 @@ struct PairingIntroView: View {
|
||||
}
|
||||
.buttonStyle(.borderedProminent)
|
||||
|
||||
// Feedback after a scan: the whole UI is still the intro until pairing completes, so
|
||||
// without this a failed or in-flight pair looks like "nothing happened."
|
||||
pairingStatus
|
||||
|
||||
VStack(spacing: 6) {
|
||||
Button { store.enterDemo() } label: {
|
||||
Label("Explore a demo — no Mac needed", systemImage: "play.circle")
|
||||
@@ -151,4 +155,26 @@ struct PairingIntroView: View {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A status line reflecting an in-flight or failed pairing attempt. Silent in the resting
|
||||
/// `.unpaired` state (the intro copy already covers that).
|
||||
@ViewBuilder
|
||||
private var pairingStatus: some View {
|
||||
switch store.connectivity {
|
||||
case .connecting, .reconnecting:
|
||||
Label("Connecting to your Mac…", systemImage: "arrow.triangle.2.circlepath")
|
||||
.font(.footnote).foregroundStyle(.secondary)
|
||||
case .hostOffline:
|
||||
Label("Can't reach your Mac. Make sure it's on the same Wi‑Fi and remote access is on.",
|
||||
systemImage: "wifi.exclamationmark")
|
||||
.font(.footnote).foregroundStyle(.orange)
|
||||
.multilineTextAlignment(.center).padding(.horizontal, 32)
|
||||
case .failed(let message):
|
||||
Label(message, systemImage: "exclamationmark.triangle.fill")
|
||||
.font(.footnote).foregroundStyle(.red)
|
||||
.multilineTextAlignment(.center).padding(.horizontal, 32)
|
||||
case .connected, .unpaired:
|
||||
EmptyView()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user