mirror of
https://github.com/permissionlesstech/bitchat.git
synced 2026-11-01 07:53:26 +00:00
Review fixes: gate nostr availability on a stored recipient key
- Codex P2 / review: a mutual favorite with a nil peerNostrPublicKey still reported .nostrAvailable, so the DM caption could claim "end-to-end encrypted" with neither a Noise session nor a usable Nostr key. BitchatPeer.connectionState (and the header fallback in PrivateConversationModel) now require a stored recipient key — mirroring NostrTransport's reachability rule. Regression test: mutual + nil key → .offline; key present → .nostrAvailable. - Copy: "local-only identity" → "device-local identity" (all 30 locales) — once a fingerprint or QR is shared it isn't "only" local; device-local says where the keys live without over-claiming. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
d723e5152a
commit
e3b22f3598
@ -357,7 +357,10 @@ final class PrivateConversationModel: ObservableObject {
|
||||
}
|
||||
if let noiseKey = Data(hexString: headerPeerID.id),
|
||||
let favoriteStatus = FavoritesPersistenceService.shared.getFavoriteStatus(for: noiseKey),
|
||||
favoriteStatus.isMutual {
|
||||
favoriteStatus.isMutual,
|
||||
// No stored recipient key means no Nostr delivery — and the
|
||||
// "end-to-end encrypted" caption keys off .nostrAvailable.
|
||||
favoriteStatus.peerNostrPublicKey != nil {
|
||||
return .nostrAvailable
|
||||
}
|
||||
if chatViewModel.meshService.isPeerConnected(headerPeerID) || chatViewModel.connectedPeers.contains(headerPeerID) {
|
||||
|
||||
@ -11560,181 +11560,181 @@
|
||||
"ar" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "هوية محلية فقط"
|
||||
"value" : "هوية محفوظة على الجهاز"
|
||||
}
|
||||
},
|
||||
"bn" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "শুধুই স্থানীয় পরিচয়"
|
||||
"value" : "ডিভাইসে থাকা পরিচয়"
|
||||
}
|
||||
},
|
||||
"de" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "nur lokale identität"
|
||||
"value" : "gerätelokale identität"
|
||||
}
|
||||
},
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "local-only identity"
|
||||
"value" : "device-local identity"
|
||||
}
|
||||
},
|
||||
"es" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "identidad solo local"
|
||||
"value" : "identidad local del dispositivo"
|
||||
}
|
||||
},
|
||||
"fa" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "هویت فقط محلی"
|
||||
"value" : "هویت محلی دستگاه"
|
||||
}
|
||||
},
|
||||
"fil" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "pagkakakilanlang lokal lamang"
|
||||
"value" : "pagkakakilanlang nasa device"
|
||||
}
|
||||
},
|
||||
"fr" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "identité locale uniquement"
|
||||
"value" : "identité locale à l'appareil"
|
||||
}
|
||||
},
|
||||
"he" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "זהות מקומית בלבד"
|
||||
"value" : "זהות מקומית במכשיר"
|
||||
}
|
||||
},
|
||||
"hi" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "केवल-स्थानीय पहचान"
|
||||
"value" : "डिवाइस-स्थानीय पहचान"
|
||||
}
|
||||
},
|
||||
"id" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "identitas hanya lokal"
|
||||
"value" : "identitas lokal perangkat"
|
||||
}
|
||||
},
|
||||
"it" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "identità solo locale"
|
||||
"value" : "identità locale al dispositivo"
|
||||
}
|
||||
},
|
||||
"ja" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "ローカル限定のアイデンティティ"
|
||||
"value" : "端末ローカルのアイデンティティ"
|
||||
}
|
||||
},
|
||||
"ko" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "이 기기에만 있는 신원"
|
||||
"value" : "기기 로컬 신원"
|
||||
}
|
||||
},
|
||||
"ms" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "identiti setempat sahaja"
|
||||
"value" : "identiti setempat peranti"
|
||||
}
|
||||
},
|
||||
"ne" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "स्थानीय-मात्र परिचय"
|
||||
"value" : "यन्त्र-स्थानीय परिचय"
|
||||
}
|
||||
},
|
||||
"nl" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "alleen-lokale identiteit"
|
||||
"value" : "apparaat-lokale identiteit"
|
||||
}
|
||||
},
|
||||
"pl" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "tożsamość tylko lokalna"
|
||||
"value" : "tożsamość lokalna urządzenia"
|
||||
}
|
||||
},
|
||||
"pt" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "identidade apenas local"
|
||||
"value" : "identidade local do dispositivo"
|
||||
}
|
||||
},
|
||||
"pt-BR" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "identidade apenas local"
|
||||
"value" : "identidade local do dispositivo"
|
||||
}
|
||||
},
|
||||
"ru" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "идентичность только на устройстве"
|
||||
"value" : "идентичность на устройстве"
|
||||
}
|
||||
},
|
||||
"sv" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "endast lokal identitet"
|
||||
"value" : "enhetslokal identitet"
|
||||
}
|
||||
},
|
||||
"ta" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "உள்ளூர்-மட்டும் அடையாளம்"
|
||||
"value" : "சாதன-உள்ளூர் அடையாளம்"
|
||||
}
|
||||
},
|
||||
"th" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "ตัวตนเฉพาะในเครื่อง"
|
||||
"value" : "ตัวตนในเครื่อง"
|
||||
}
|
||||
},
|
||||
"tr" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "yalnızca yerel kimlik"
|
||||
"value" : "cihaza yerel kimlik"
|
||||
}
|
||||
},
|
||||
"uk" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "ідентичність лише на пристрої"
|
||||
"value" : "ідентичність на пристрої"
|
||||
}
|
||||
},
|
||||
"ur" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "صرف مقامی شناخت"
|
||||
"value" : "ڈیوائس مقامی شناخت"
|
||||
}
|
||||
},
|
||||
"vi" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "danh tính chỉ trên thiết bị"
|
||||
"value" : "danh tính cục bộ trên thiết bị"
|
||||
}
|
||||
},
|
||||
"zh-Hans" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "仅限本机的身份"
|
||||
"value" : "设备本地身份"
|
||||
}
|
||||
},
|
||||
"zh-Hant" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "僅限本機的身分"
|
||||
"value" : "裝置本機身分"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -33,13 +33,22 @@ struct BitchatPeer: Equatable {
|
||||
return .bluetoothConnected
|
||||
} else if isReachable {
|
||||
return .meshReachable
|
||||
} else if favoriteStatus?.isMutual == true {
|
||||
// Mutual favorites can communicate via Nostr when offline
|
||||
} else if favoriteStatus?.isMutual == true, reachableNostrPublicKey != nil {
|
||||
// Mutual favorites can communicate via Nostr when offline — but
|
||||
// only with a stored recipient key. NostrTransport applies the
|
||||
// same rule when computing reachable peers; without a key,
|
||||
// "available" would be a lie, and the DM header's
|
||||
// "end-to-end encrypted" caption keys off this state.
|
||||
return .nostrAvailable
|
||||
} else {
|
||||
return .offline
|
||||
}
|
||||
}
|
||||
|
||||
/// The Nostr key a private envelope would actually be sealed to, if any.
|
||||
var reachableNostrPublicKey: String? {
|
||||
nostrPublicKey ?? favoriteStatus?.peerNostrPublicKey
|
||||
}
|
||||
|
||||
var isFavorite: Bool {
|
||||
favoriteStatus?.isFavorite ?? false
|
||||
|
||||
@ -36,6 +36,30 @@ struct BitchatPeerTests {
|
||||
#expect(peer.statusIcon == "🌙")
|
||||
}
|
||||
|
||||
@Test("Mutual favorite without a stored Nostr key is offline, not nostr-available")
|
||||
func mutualFavoriteWithoutNostrKeyIsOffline() {
|
||||
let peerID = PeerID(str: "0123456789abcdef")
|
||||
let noiseKey = Data((0..<32).map(UInt8.init))
|
||||
var peer = BitchatPeer(peerID: peerID, noisePublicKey: noiseKey, nickname: "A", isConnected: false, isReachable: false)
|
||||
peer.favoriteStatus = FavoriteRelationship(
|
||||
peerNoisePublicKey: noiseKey,
|
||||
peerNostrPublicKey: nil,
|
||||
peerNickname: "A",
|
||||
isFavorite: true,
|
||||
theyFavoritedUs: true,
|
||||
favoritedAt: Date(timeIntervalSince1970: 1),
|
||||
lastUpdated: Date(timeIntervalSince1970: 2)
|
||||
)
|
||||
|
||||
// Nothing to seal a Nostr envelope to: claiming availability here
|
||||
// would relight the DM header's "end-to-end encrypted" caption with
|
||||
// neither a Noise session nor a usable recipient key.
|
||||
#expect(peer.connectionState == .offline)
|
||||
|
||||
peer.nostrPublicKey = "abcdef"
|
||||
#expect(peer.connectionState == .nostrAvailable)
|
||||
}
|
||||
|
||||
@Test("Mutual offline peers show Nostr icon")
|
||||
func mutualFavoriteOfflinePeerShowsNostrIcon() {
|
||||
let peerID = PeerID(str: "0011223344556677")
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user