mirror of
https://github.com/permissionlesstech/bitchat.git
synced 2026-07-26 15:05:24 +00:00
1dc2150a9f67beb68571f9942f43468706fa4750
42
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
40238c5e43 | Harden private media migration compatibility | ||
|
|
74b8a47d02 |
Empty-mesh liveliness: nearby conversations, echoes, wave, dead drops, radar (#1409)
* Empty-mesh liveliness: nearby conversations, echoes, wave action, dead drops, radar The empty mesh timeline was a dead end: a grey zero and "nobody in range yet". This turns it into a live surface and gives the app pull when the mesh wakes up: - Nearest conversation: background geohash sampling now tracks actual chat messages (not just presence) per regional channel; the empty state surfaces the busiest nearby conversation with a preview, one tap to join (GeohashChatActivityTracker, fed from GeoPresenceTracker). - Echoes: the carried 6h store-and-forward window renders as dimmed "heard here earlier" rows at launch (new Transport collectArchivedPublicMessages -> GossipSyncManager snapshot, decoded with signature-derived nicknames; content-identity dedup guards against re-synced duplicates). - Wave: the "bitchatters nearby" notification gains a "wave" quick action that broadcasts a mesh 👋 straight from the notification, even backgrounded (first UNNotificationCategory in the app). - Dead drops: /drop pins a note to the current building geohash as a kind-1 location note with a 24h NIP-40 expiry; expired notes are now dropped client-side at ingest; the notices sheet shows "fades in Xh"; a "location notes" toggle plus location-permission controls live in app info (also fixes the duplicated Voice section). - Radar: an ambient sonar animation shows the radio scanning, with a privacy-safe daily tally ("N devices passed within range today" via salted per-day hashes) and a "notes left here" hint that opens the notices geo tab. All new user-facing strings ship in all 29 locales. 1403 tests green, including new suites for the activity tracker, sightings tally, and note expiry/drop publishing. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * Review round: app-info polish, urgent/expiry parity, centered radar, pin fill, Codex P2s - App info: LOCATION header uppercased like sibling sections; location notes and live voice descriptions shortened (29 locales); redundant "location access granted" line removed. - Notices parity: urgent + expiry controls now show on the geo tab too; the bridged Nostr note carries ["t","urgent"] and NIP-40 so relay-side readers see both; urgent parsed back from incoming notes. - Radar moved from the top of the empty state to the center of the chat area, below the help text (empty state fills the visible height). - Header pin fills whenever the scope has notices (was: only unseen), and Nostr-only nearby notes now light it too. - Codex P2 fixes: notification completion deferred until the wave action is handled (background suspension dropped the send); NIP-40 notes now prune on a timer when they expire while displayed; the location-notes kill switch retargets the nearby-notes counter immediately. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * Hide macOS segmented picker's built-in label in notices composer (duplicate 'expires in') Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * Geo notes: permanent (∞) expiry default, urgent stays mesh-only - Geo expiry picker gains ∞ as the default: a permanent note posts as a pure relay note (no NIP-40 tag, no mesh-board copy — a board copy must fade within days, contradicting the ∞ the user picked). 1/3/7d keep the board + bridged-note path with NIP-40. - The notes manager is now owned by the notices sheet (not the list) so the composer local-echoes ∞ notes into the list; it revives via refresh() after a tab-switch cancel, and its expiry-prune timer survives cancel (weak self, dies with the instance). - Urgent toggle returns to mesh-only per review — notes are ambient; the read-side urgent-tag parse stays so tagged notes still render. - macOS: hide the segmented picker's built-in label (duplicate "expires in"). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * Clearing the mesh timeline dismisses echoes for good; tighter divider copy Triple-tap /clear emptied the timeline but the next launch re-seeded "heard here earlier" from the persisted archive. A MeshEchoSettings watermark now records the clear; only messages heard after it come back (the archive itself still carries everything for peers' sync). The echo dedup keys reset with it, and panic wipe drops the watermark. Divider copy tightened to "heard here earlier · last 6h" (29 locales). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * Echoes visual polish: tinted history block, radar-captioned tally, ambient footer Device-test feedback on the echoes screen: - Archived echoes now sit on a subtle tinted background (secondary at 8%) in addition to the dim, so "heard here earlier" reads as one distinct block; the divider carries the echo ID prefix to join it. - "N devices passed within range today" moves out of the narration lines to sit centered under the radar as its caption. - When the timeline holds only echoes/system lines, a compact ambient footer (small radar + tally + live hints) renders below the history instead of the whole ambient layer vanishing. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * Notes strip persists above the mesh chat; leaner empty-state narration - The 📍 "notes left here" line was empty-state-only, so starting a conversation hid it. It is now a tappable strip pinned above the mesh timeline whenever unexpired notes exist at this place (opens the notices geo tab); the nearby-notes counter runs for the whole mesh timeline, not just the empty state. - Empty state narration drops "nobody in range yet..." (the radar and the sightings caption already say it) and the nearby-conversation hint moves below the help line instead of splitting the narration. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * Radar means searching: hide the sweep once mesh peers are connected or reachable Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: jack <jackjackbits@users.noreply.github.com> Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
ef848857b7 |
Remove dead code found by full Periphery audit; add scan config + advisory CI (#1410)
Periphery 3.7.4 audit of both schemes (macOS + iOS, intersected so platform-specific code is never touched), with test targets indexed and the share extension built. 277 dead declarations removed or demoted: dead forwarding wrappers (ChatViewModel+Nostr/+PrivateChat), removed- feature remnants (autocomplete command suggestions, back-swipe tuning, MediaSendError, GeohashParticipantTracker), unused Tor dormancy bindings, assign-only properties, unused parameters (renamed to _), and redundant public accessibility. 13 orphaned localization keys deleted across all 29 locales (old pre-#1392 location-notes UI, app_info warnings). Two real tests were flagged as unused because they never ran: Swift Testing methods missing @Test (NostrProtocolTests. testAckRoundTripNIP44V2_Delivered, NotificationStreamAssemblerTests. testAssemblesCompressedLargeFrame). Re-armed both; they pass. Deliberately kept, now recorded in .periphery.baseline.json: iOS-only code invisible to the CI macOS scan, C FFI signatures, keep-alive NWPathMonitor reference, InboundEventKey.eventID (dedup semantics), wifiBulk capability bit (reserved for Wi-Fi bulk work, used by BitFoundation package tests), and the String secureClear cluster (exercised by package tests). New: .periphery.yml config and an advisory Dead Code CI job (mirrors the SwiftLint precedent from #1361) that fails on findings not in the committed baseline. Verified: full macOS app suite, BitFoundation (119) and BitLogger (13) package tests green; periphery scan --strict exits clean. Co-authored-by: jack <jackjackbits@users.noreply.github.com> Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
81a10f73f0 |
Private groups: creator-managed encrypted group chat over the mesh (#1383)
* Add capability bits to announce TLV Announces now carry an optional capabilities TLV (0x05): a little-endian bitfield with named bits for upcoming features (prekeys, wifiBulk, gateway, groups, board, vouch, meshDiagnostics). Old clients skip the unknown TLV; peers without it decode as nil so features can distinguish "legacy peer" from "advertises nothing". PeerCapabilities lives in BitFoundation with a minimal-length encoding that preserves unknown bits for forward compatibility. Peer capabilities are stored in the BLE peer registry on verified announce and exposed via BLEService.peerCapabilities(_:). The local advertisement set is empty until each feature ships its bit. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * Private groups: creator-managed encrypted group chat over the mesh Small encrypted crews (hard cap 16) between public broadcast and 1:1 DMs: Protocol - MessageType.groupMessage = 0x25: broadcast packets with a cleartext 16-byte group ID + epoch, ChaCha20-Poly1305 ciphertext (epoch bound as AEAD AAD), inner Ed25519 sender signature over "bitchat-group-msg-v1"|groupID|messageID|timestamp|content - NoisePayloadType.groupInvite = 0x06 / .groupKeyUpdate = 0x07: creator-signed group state (key, epoch, roster) 1:1 over Noise; signature over "bitchat-group-v1"|groupID|epoch|key-hash|roster-hash and the Noise session peer must BE the creator - SyncTypeFlags bit 10 (groupMessage): variable-length LE bitfield widens 1 -> 2 bytes inside the length-prefixed REQUEST_SYNC TLV; old clients ignore unknown bits and answer with types they know - PeerCapabilities.localSupported now advertises .groups Storage - GroupStore: symmetric keys in the keychain, roster/name/epoch as protected JSON in Application Support; wiped in panicClearAllData() Behavior - Non-members relay 0x25 like any broadcast but cannot read it; group messages join gossip-sync backfill with the public-message window - Receivers drop wrong-epoch envelopes, bad sender signatures, and senders missing from the creator-signed roster - Fire-and-flood delivery (no per-member acks in v1) UI - Groups open as chat windows through the private-chat sheet (virtual "group_" peer IDs); groups section in the people sheet; /group create/invite/remove/leave/list commands; invitees get a system message + notification and the group appears in their people sheet Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * Private groups: fix TLV truncation, roster downgrade, removal notice, block, media, signable bytes Addresses the Codex review and adversarial-review findings on #1383: - TLV encoding now throws GroupTLVError.valueTooLong instead of clamping to 65535 and truncating, so an oversize group message fails to seal and surfaces send_failed rather than shipping ciphertext recipients drop. - Roster nicknames truncate on a Character boundary (never mid-scalar), so a multi-byte nickname can no longer make the whole signed roster undecodable. - Invites now bump the epoch (rotate the key) like removals, giving every roster change a strictly-increasing epoch so out-of-order invite states no longer last-writer-wins a just-added member back out. - Removing a member now sends them a creator-signed roster-without-them under a throwaway all-zero key (never the rotated key), so their client deactivates the group and surfaces "removed" instead of going silently dark. - /block is enforced in the group receive path: a blocked member's messages are dropped from display and notifications, consistent with every other inbound path. - Media affordances are disabled in group chats (both computed sites) so the composer can't strand a media placeholder that never sends; media-in-groups is a documented v2 item. - Creator signature now covers the group name and the sender signature covers the epoch (wire-format-affecting; needs Android parity before ship). - Explicit isGroup guard in markPrivateMessagesAsRead so read/delivered receipts can never leak into group conversations under a future refactor. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: jack <jackjackbits@users.noreply.github.com> Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
f9032cf2b9 |
Add mesh diagnostics: /ping, /trace, and topology map (#1377)
* Add mesh diagnostics: /ping, /trace, and topology map - New protocol types ping=0x26 / pong=0x27 (9-byte payload: 8-byte nonce + origin TTL) with per-peer inbound rate limiting (5 per 10s) - /ping @name reports RTT and hop count, 10s timeout - /trace @name prints the estimated path from gossiped directNeighbors - Topology map sheet (circular Canvas layout) reachable from App Info - Ping/pong ride the deterministic directed-relay path like DMs - Tests: payload round-trip, hop-count math, command output, edge normalization, layout Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * Fix CI media-wipe race, per-link ping rate limiting, and /ping output routing Three fixes for PR #1377 review: 1. CI flake (sendImage_privateChatProcessesAndTransfersImage): the panicClearAllData / clearCurrentPublicTimeline detached utility-priority tasks delete the real ~/Library/Application Support/files tree, which the test process shares. The wipe fires at a nondeterministic time and raced the sendImage test's JPEG in files/images/outgoing (write then re-read), so prepareImagePacket threw and the test timed out. Both wipes are now skipped under tests (existing TestEnvironment.isRunningTests pattern); this also stops test runs from deleting the developer's real media. 2. Codex P1: ping packets are unsigned, so keying the pong rate limiter on packet.senderID let one connected peer rotate forged sender IDs to bypass the 5-per-10s budget. The limiter now keys on the ingress link (the directly connected peer that delivered the packet); the pong still goes to the claimed sender. Regression test proves rotating senders over one link exhaust one budget (fails 10 vs 5 pongs on the old code). 3. Codex P2: /ping output arrived up to 10s later and was routed from selectedPrivateChatPeer at callback time, misrouting the result after a chat switch. The origin conversation is now captured when the command is issued (CommandOutputDestination) and deferred output is routed there: a DM result lands in the origin chat's history even if deselected, and a mesh-timeline result pins to #mesh instead of the active channel. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * App Info: move NETWORK section under HOW TO USE and uppercase NETWORK/SYMBOLS headers Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * Conform DiagnosticsMockContext to sendPublicMessage CommandContextProvider gained sendPublicMessage (Cashu /pay, #1376) after this branch forked, so the diagnostics test mock no longer conformed once main was merged in. Add the no-op stub. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: jack <jackjackbits@users.noreply.github.com> Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
70229f0be1 |
Originate v2 source routes and wire fragmentIdFilter targeted resync (#1378)
* Originate v2 source routes and wire fragmentIdFilter targeted resync Part A — source-route origination policy: - Gate route application (BLESourceRouteOriginationPolicy): only packets we author, directed at a single peer, with TTL headroom, whose recipient is not directly connected. Relays no longer attach routes to (and re-sign) packets they merely forward. - Version-gate paths: MeshTopologyTracker records the highest protocol version observed per peer; BFS routes require every intermediate hop and the recipient to be v2-observed, capped at 4 intermediate hops. - Degrade on failure: BLESourceRouteFailureCache marks a routed send that sees no inbound traffic from the recipient within 10s as failed and floods for 60s before retrying routes. Part B — REQUEST_SYNC fragmentIdFilter (TLV 0x06): - Requester: BLEFragmentAssemblyBuffer reports stalled broadcast reassemblies (no new fragment for 5s, retried at most every 10s); the maintenance pass sends a types=fragment REQUEST_SYNC naming the stalled 8-byte fragment stream IDs to each connected peer. - Responder: GossipSyncManager restricts the fragment diff to exactly the named streams, bypassing the since-cursor while the GCS filter still excludes pieces the requester holds; RSR/TTL-0/rate-limit semantics unchanged and REQUEST_SYNC stays link-local. - Bounds: at most 60 IDs per request (60*17-1 = 1019 bytes <= the 1024-byte decoder cap); oversized 0x06 values are ignored, not fatal. Docs: SOURCE_ROUTING.md gains the iOS origination policy (§8); REQUEST_SYNC_MANAGER.md documents 0x05/0x06 as implemented. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * Fix stall-clock refresh on duplicates and overflow suppression in fragment resync Two fixes to stalledBroadcastFragmentIDs bookkeeping in BLEFragmentAssemblyBuffer: - Duplicate fragments no longer reset the stall clock. Fragment packets bypass the packet deduplicator, so relayed duplicates of an already-held index arriving every few seconds kept lastFragmentAt fresh and suppressed the targeted REQUEST_SYNC indefinitely. Now lastFragmentAt only updates when the index is new (actual progress). - Only the streams that will actually be encoded on the wire are rate-limited. Previously every stalled candidate got lastResyncRequestAt set, but encodeFragmentIdFilter serializes at most RequestSyncPacket.maxFragmentIdFilterCount (60) IDs, so overflow streams were suppressed for retryAfter without ever being requested. Selection now caps at that shared constant, oldest stall first, so overflow stays eligible and rotates fairly on the next pass. Tests: duplicates arriving periodically still trigger the stall report; 70 stalled streams yield the 60 oldest on the first pass and the remaining 10 on the next. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: jack <jackjackbits@users.noreply.github.com> Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
3c610a83cd |
Cashu ecash chips: detect, render, and redeem tokens + /pay command (#1376)
* Cashu ecash chips: detect, render, and redeem tokens + /pay command Content-level Cashu support, no wire-protocol changes: - CashuTokenDecoder: summarizes V3 (cashuA base64url-JSON) tokens — amount summed across proofs, unit, mint host, memo — and V4 (cashuB) via a minimal bounded CBOR reader. All input is treated as adversarial: size caps, depth/item budgets, overflow guards, display sanitization; malformed payloads fail closed to a generic chip. - PaymentChipView: cashu chips now show "500 sat · mint.example.com" (+ memo) instead of a generic label; tap opens a cashu: wallet URL and falls back to https://redeem.cashu.me when no wallet handles it; context menu adds copy token / redeem in wallet / redeem on web. - extractCashuLinks now returns bare deduplicated bearer strings so the chip can decode them (cashu: URIs still detected via the embedded token). - /pay <token>: validates the token decodes, sends it as the message body; DMs send directly, public channels require an explicit "/pay <token> public" confirm since tokens are bearer instruments. Suggested everywhere except public geohash channels. - Tests: decoder (V3/V4 decode, summation, URI forms, truncation/ garbage/huge fuzzing, CBOR depth bounds) and /pay command flows. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * Cashu: strict decode on the /pay SEND path The permissive decoder turned any non-empty cashuB… base64 that failed CBOR parsing into a generic TokenInfo, so the /pay guard accepted base64 junk and truncated V4 tokens and relayed them with a success message. Add a `strict` flag to CashuTokenDecoder.decode: in strict mode there is no permissive V4 fallback and the token must resolve to a known version with a positive amount, else it returns nil. Rendering keeps the permissive path (an unknown chip is fine for display). /pay now decodes with strict:true and surfaces "invalid cashu token" instead of sending. Tests: /pay with truncated cashuB / base64 junk is rejected; valid V3 and valid definite-length V4 still send; decoder strict-mode unit tests. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: jack <jackjackbits@users.noreply.github.com> Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
c2a5668569 |
Sync cleanups: normalize SyncTypeFlags, single announce-ID path, TODO (#1373)
Follow-ups deferred from the REQUEST_SYNC review (#1371): - SyncTypeFlags.init(rawValue:) now masks to the union of bits that map to a known message type (derived from the bit↔type table, so it tracks new types automatically). Phantom bits from a truncated/garbled flags field — or a type a newer peer added — no longer live in the set as membership no contains() matches yet toData() re-serializes. - GossipSyncManager stored each latest announce as (hex-id string, packet) and diffed announces against the stored string while every other type recomputed the ID via PacketIdUtil. Collapsed the store to just the packet and recompute the ID everywhere, removing the latent dual-path divergence. - Documented the REQUEST_SYNC TLV table and marked fragmentIdFilter (0x06) with a TODO(v2): it's parsed/re-serialized but never populated or honored (reserved for incremental fragment sync) — finish or drop, not silent dead surface. Adds SyncTypeFlags phantom-bit/round-trip tests and a GossipSyncManager test that an announce already in the requester's filter is suppressed (guards the recompute path). Full suite: 1034 tests pass. Co-authored-by: jack <jackjackbits@users.noreply.github.com> Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com> |
||
|
|
e191e9c6f2 |
Fix slash-command suggestions that insert commands the processor rejects (#1354)
The autocomplete panel is the only in-app surface for discovering slash
commands, but several suggestions do not match what CommandProcessor
accepts, so tapping them inserts a command that returns "unknown
command":
- CommandInfo suggests /dm, /favorite, /unfavorite, but the processor
only handles /m, /msg, /fav, /unfav. Aliases are aligned to the
accepted spellings (msg, fav, unfav).
- Favorites are suggested only in geohash contexts (isGeoPublic ||
isGeoDM) — exactly where the processor rejects them ("favorites are
only for mesh peers"). The gating is inverted so they appear in mesh,
where they work.
Also, small related fixes to the discovery surface:
- /help is now handled (the ChatViewModel command docstring already
claimed it existed); it prints a local system line listing the valid
commands, and the unknown-command error points at it.
- The suggestion panel keeps the matched command's usage row (e.g.
"/msg <nickname>") visible while arguments are typed, instead of
vanishing at the first space; in that mode the row is informational
and no longer overwrites the draft on tap.
New string is added source-language (en) only. The CommandInfo contract
test is updated to the corrected metadata.
|
||
|
|
6bda919dd4 |
Fix transport reliability gaps: Tor stalls, weak-signal sends, GCS input validation
NostrRelayManager no longer strands work when Tor is slow to bootstrap: failed readiness waits retry (bounded by nostrTorReadyMaxWaitAttempts) instead of dropping queued relay connections, parked EOSE callbacks fire after exhaustion so callers never hang, and sends made before Tor is ready are queued locally (capped) instead of being dropped on a failed wait - still strictly fail-closed. MessageRouter now prefers a connected transport over a merely window-reachable one, and sends made on a weak reachability signal are retained in the outbox until a delivery/read ack confirms receipt (receivers dedup by message ID), with resends bounded by attempt count. GCS sync filters from the wire are bounds-checked (p in 1...32, m > 1) at both the packet decode and filter decode layers; oversized Golomb parameters previously decoded to garbage via silent shift overflow. BLELinkStateStore is now explicitly pinned to bleQueue: debug builds trap any access from another queue, enforcing the ownership discipline the surrounding code already relied on by convention. CI gains an iOS simulator build job (arm64 only; the vendored Arti xcframework has no x86_64 simulator slice) so iOS-conditional code paths are compile-checked - SPM tests only cover the macOS slice. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
c60eff2c11 | Move additional files/tests to BitFoundation (#1102) | ||
|
|
4cfcefcda6 |
BitFoundation module to centralize shared components (#1089)
* Run local packages’ tests as well on CI * BitFoundation module to centralize shared components |
||
|
|
473f5c7cce |
String trimming in a centralized manner (#1079)
* String trimming in a centralized manner * Fix empty-nickname case |
||
|
|
097d916da7 |
Refactor and Extract Media-related code (#1077)
* Extract media-related code from ContentView * Refactor hard-coded prefix and directory names |
||
|
|
3fc64f6168 |
feat: Implement Request Sync Manager (V2 Sync) (#965)
* feat: Implement Request Sync Manager (V2 Sync) - Add RequestSyncManager to track and attribute sync requests - Update BitchatPacket and BinaryProtocol to support IS_RSR flag (0x10) - Update RequestSyncPacket with new TLV fields (sinceTimestamp, fragmentIdFilter) - Update GossipSyncManager to use unicast sync requests and mark responses as RSR - Update BLEService to enforce timestamp validation for normal packets and exempt valid RSRs - Add documentation for the new sync manager mechanism * fix: Resolve compilation errors in V2 Sync implementation - Remove duplicate restartGossipManager in BLEService - Add missing TransportConfig constants for sync - Add 'sync' log category to BitLogger - Add missing BitLogger import in GossipSyncManager * fix: Update tests for V2 Sync changes - Add requestSyncManager parameter to GossipSyncManager init in tests - Implement getConnectedPeers stub in RecordingDelegate - Remove unused variable warning in SubscriptionRateLimitTests --------- Co-authored-by: a1denvalu3 <> Co-authored-by: jack <212554440+jackjackbits@users.noreply.github.com> |
||
|
|
97fc21c23f |
Add localizable strings in show commands view (#828)
* Add localizable strings in show commands view n refactor code to remove duplicate string declarations. * Modify struct to enum in separate file to Models/CommandsInfo. * Add corrections code in refactory and enum. * Correct code in command enum and ContentView. * Dedicated CommandSuggestionsView to extract code * Simplify CommandInfo + minor optimization * Fix preview --------- Co-authored-by: islam <2553451+qalandarov@users.noreply.github.com> |
||
|
|
01256f3041 |
Add source-based routing support (#862)
* Add source-based routing support * include neighbors in ANNOUNCE --------- Co-authored-by: jack <jackjackbits@users.noreply.github.com> Co-authored-by: callebtc <93376500+callebtc@users.noreply.github.com> |
||
|
|
b6ce4fae43 |
Add type-aware REQUEST_SYNC sync rounds (#853)
Co-authored-by: jack <jackjackbits@users.noreply.github.com> |
||
|
|
b839ce5f6c |
PeerID 31/n: Remove String interop to be explicit (#840)
* PeerID 28/n: `ChatViewModel.getShortIDForNoiseKey` * PeerID 29/n: `BLEService` + remove dupe funcs from #823 * `handleFileTransfer` to use PeerID * `sendMessage` and `sendPrivateMessage` * PeerID 30/n: Update some leftovers * `lowercased()` inside PeerID for normalization * PeerID 31/n: Remove String interop to be explicit * MockBLEService: Remove direct target delivery This causes a delivery even if the sender and receiver are not connected --------- Co-authored-by: jack <jackjackbits@users.noreply.github.com> |
||
|
|
0776c9813c |
PeerID 30/n: Update some leftovers + case normalization (#839)
* PeerID 28/n: `ChatViewModel.getShortIDForNoiseKey` * PeerID 29/n: `BLEService` + remove dupe funcs from #823 * `handleFileTransfer` to use PeerID * `sendMessage` and `sendPrivateMessage` * PeerID 30/n: Update some leftovers * `lowercased()` inside PeerID for normalization --------- Co-authored-by: jack <212554440+jackjackbits@users.noreply.github.com> |
||
|
|
435744a977 |
PeerID 25/n: ReadReceipt (#832)
|
||
|
|
40e54a5120 |
Add voice notes and images over BLE mesh (audio + image only) (#823)
* Add BLE file transfer support and media UX * Gracefully disable mac attachment pickers in sandbox * Tighten spacing above media message bubbles * Reduce vertical padding between chat rows * Restore iOS file importer for attachments * Copy imported files before sending to preserve access * Allow file transfers from connected but unverified peers * Raise BLE notification buffer cap for large file transfers * Revert "Raise BLE notification buffer cap for large file transfers" This reverts commit b624523af843475db84e4a846db8dcbe824ae408. * Add guard to drop oversized BLE notification assemblies * Let BLE assembler accept large frames up to hard cap * Add detailed logging for BLE fragment assembly * Log incomplete BLE frames for debugging * Stop dropping partial BLE frames while assembling notifications * Fix compressed BLE file transfers * Enable mac attachment importers * Allow mac microphone access * Permit mac media library access * Describe microphone usage * Harden attachment transfer bookkeeping * Display recording milliseconds * Restore mac photo picker access * Use Photos picker on mac * Allow long-press reblur on images * Reblur images via swipe * Lowercase image preview buttons * Keep processed images for outgoing messages * Use save panel for mac image export * Fix image attachment detection * Allow user-selected write access * Align mac image JPEG encoding * Revert unsupported JPEG option * Strip metadata in mac image encoding * Normalize mac JPEG color space * Target image byte size across platforms * Fix CFMutableData handling * Preserve packet version when signing * Use unique transfer identifiers * Stub file transfer methods in mock * Stub file transfer methods in mock * Hide absolute paths in media messages * Resolve image/voice path handling * Fix cleanupLocalFile lookup * Restore BLE broadcasts when notify buffer is saturated * Guard peer map reads on BLE message path * Drop attachment ceilings to 1 MiB and bump release version * Reset BLE assembler on stalled fragment trains * Fix binary protocol test fixtures * Fix critical issues from PR #681 review Critical fixes: - BinaryProtocol: Return nil for unknown versions (prevents buffer underflows) - Add BinaryProtocol.Offsets struct to centralize magic numbers - Replace magic offset calculations with named constants Security/Privacy: - FileAttachmentView: Use url.lastPathComponent instead of url.path (prevents exposing full system paths) Documentation: - Fix compression algorithm documentation (zlib, not LZ4) All tests passing. * Fix UI freeze when receiving voice notes Problem: AVAudioPlayer initialization in VoiceNotePlaybackController.init() was running synchronously on main thread during view creation, blocking UI for 50-200ms per voice note. Solution: - Remove eager preparePlayer() call from init - Load duration asynchronously on background queue - Player is only prepared when playback is actually requested via ensurePlayerReady() This prevents UI freezes when voice notes appear in the chat. * Fix memory leaks and post-playback freeze Fixes: 1. Post-playback freeze: audioPlayerDidFinishPlaying now dispatches to main thread before updating @Published properties (Swift concurrency violation) 2. Unbounded waveform cache: Implement LRU eviction with 20-entry limit - Track last access time for each cached waveform - Evict oldest entry when cache is full - Prevents unlimited memory growth as voice notes accumulate 3. Audio buffer memory leaks: Wrap computeWaveform in autoreleasepool - AVAudioPCMBuffer allocations are autoreleased - Pool ensures buffers are freed promptly 4. Image processing memory: Add autoreleasepool around compression loops - Each jpegData() call creates temporary objects - Inner pool per iteration prevents memory spikes during quality search Memory should now remain stable during extended use. * Eliminate disk I/O from SwiftUI view rendering path Critical performance fix for UI freezes when receiving media: Problem: mediaAttachment(for:) was called during every SwiftUI render, performing synchronous disk I/O on main thread: - FileManager.fileExists() called 2-6x per message (checking subdirs) - applicationFilesDirectory() creating directories on every call - With multiple media messages, this meant 20-100+ disk ops per render Solution: 1. Remove fileExists checks - construct URLs directly - Files are validated during playback/display (fail gracefully if missing) - Sender determines subdirectory (outgoing vs incoming) 2. Cache applicationFilesDirectory() result - Static cache prevents repeated FileManager.url() calls - Directory created only once 3. Remove redundant playback.replaceURL() in VoiceNoteView.onAppear - Controller already initialized with correct URL This eliminates ALL disk I/O from the view rendering hot path. * Cache Nostr identity derivation to prevent crypto during view rendering Critical performance fix: Problem: formatMessageHeader() called deriveIdentity(forGeohash:) during every SwiftUI render for every media message. Each call performed: - Keychain I/O (getOrCreateDeviceSeed) - HMAC-SHA256 computation - Up to 10 secp256k1 key validations (elliptic curve crypto) With multiple media messages, this resulted in 100s of milliseconds of blocking crypto on main thread per render cycle. Solution: Add thread-safe cache for derived identities - Check cache before expensive crypto operations - NSLock protects concurrent access - Identity is deterministic per geohash, so caching is safe This eliminates crypto from the hot rendering path. * Cache geohash identity in ChatViewModel to prevent crypto during rendering Additional optimization for location channels (voice notes are mesh-only, but this helps with text message rendering in geohash channels): - Add cachedGeohashIdentity to avoid deriveIdentity calls during rendering - Check cache before falling back to crypto derivation - Reduces main thread crypto work in location channels * Make voice note loading completely lazy with deferred initialization Aggressive performance optimization to prevent UI freezes: Problem: Even with async loading, creating 10+ VoiceNotePlaybackController instances simultaneously (when scrolling past multiple voice notes) spawned 20+ concurrent background tasks, potentially starving main thread. Solution - Ultra-lazy loading: 1. VoiceNotePlaybackController.init() now does ZERO work - No duration loading - No player creation - Instant initialization 2. Duration loaded on-demand via public loadDuration() method - Called from VoiceNoteView.onAppear after 150ms delay - Reduced priority: .utility instead of .userInitiated - Guard prevents duplicate loading 3. Waveform loading also deferred 150ms - Gives UI time to settle after message appears - Prevents task storms when multiple voice notes appear This spreads the work over time instead of all at once. * Ensure /clear and panic triple-tap delete media files Fix: /clear command and panicClearAllData() now properly delete media files 1. /clear (triple-tap on chat): - Deletes outgoing media (voice notes, images, files) - Conservative: only our sent media, preserves received media - Runs in background to avoid UI freeze 2. panicClearAllData() (triple-tap on bitchat/ header): - Deletes ALL media files (incoming + outgoing) - Removes entire files directory and recreates structure - Ensures complete data wipe for emergency scenarios Both operations run async on .utility queue to prevent blocking UI. * Fix infinite render loop and apply all security fixes CRITICAL BUG FIX - Infinite Render Loop: Root Cause: Duplicate view identity in ContentView.swift:368 ForEach(messageItems) { item in // Already uses item.id via Identifiable messageRow(...) .id(item.id) // ❌ REDUNDANT modifier caused identity re-evaluation loop } When @Published properties updated, SwiftUI re-evaluated .id() → appeared as 'new' identity → triggered re-render → infinite loop. Caused UI freezes, keyboard failures, and 100% CPU usage. Fix: Remove redundant .id() modifier - ForEach already has stable identity. PERFORMANCE FIXES: 1. Waveform Cache Deadlock (Waveform.swift) - Removed nested queue.async(barrier) on cache hits - Was causing task saturation and potential deadlocks 2. Async Send Pattern (ContentView.swift) - Clear input immediately, defer actual send to next runloop - Prevents blocking current event handler 3. Proper Swift Concurrency (VoiceNoteView.swift) - Switch from .onAppear + DispatchQueue to .task - Cleaner async/await pattern for loading 4. Remove Redundant objectWillChange (ChatViewModel.swift) - @Published already triggers updates automatically - Explicit send() was causing double update cycles SECURITY FIXES (C1-C5, H1-H2): C1. Path Traversal Protection (BLEService.swift) - Unicode normalization, null byte removal - Replace ALL path separators, reject dotfiles - Validate paths don't escape directory C2. Integer Overflow (BitchatFilePacket.swift) - Use UInt64 for TLV parsing, safe Int conversion C3. MIME Validation (BLEService.swift) - Whitelist: JPEG, PNG, GIF, WebP, M4A, MP3, WAV, OGG, PDF - Magic byte validation for all types - Lenient on M4A (platform variations) C4. Compression Bomb (BinaryProtocol.swift) - Ratio validation <= 50,000:1 - Defense-in-depth with 1MB size cap C5. TOCTOU Race (ChatViewModel.swift) - Direct removeItem without fileExists check H1. File Size Validation (ChatViewModel, ImageUtils) - Check attributes BEFORE Data(contentsOf:) - Prevents memory exhaustion H2. Metadata Stripping (ImageUtils.swift) - Remove ALL metadata keys from JPEG encoding - Only compression quality set - Protects GPS/EXIF/device info privacy RESULT: ✅ No render loops ✅ Works with Xcode debugger ✅ Voice notes display properly ✅ All security vulnerabilities fixed ✅ 164 tests passing Production ready. * Complete all translations to 100% and fix auto-extraction - Mark non-localizable strings with Text(verbatim:) to prevent extraction - Update UI strings to lowercase per style guide (open, save, close, recording) - Add complete translations for all 29 languages (194/194 strings at 100%) - Remove empty/duplicate entries (@, bitchat/, Open, Recording %@) - Add proper localization comments for all user-facing strings * macOS: Focus message input on launch instead of nickname field * Remove debug print statements from sendMessage * Optimize voice note codec to 16 kHz / 20 kbps for smaller file sizes - Reduce sample rate from 44.1 kHz to 16 kHz (telephony standard) - Lower bitrate from 32 kbps to 20 kbps - Results in ~37% file size reduction (~150 KB/min vs 240 KB/min) - Increases max voice note length from 4.4 to 7 minutes over 1 MiB BLE limit - Maintains excellent voice quality using native AAC-LC codec * Fix critical security issues in fragment reassembly and file cleanup Fragment Reassembly Race Condition (CRITICAL): - Wrap all incomingFragments/fragmentMetadata access in collectionsQueue.sync - Prevents concurrent modification crashes from multi-threaded access - Minimizes lock contention by doing heavy work (reassembly/decode) outside locks - Add upper bound check: reject fragments with total > 10,000 (DoS prevention) - Add cumulative size validation before storing fragments (memory DoS prevention) File Cleanup Path Traversal (CRITICAL): - Use NSString.lastPathComponent to extract filename safely - Prevents directory traversal attacks via malicious filenames - Add path prefix validation before file deletion - Now checks both incoming and outgoing directories (fixes disk leak) Additional Protections: - Fragment assemblies now limited by both count (128) and cumulative bytes (1MB) - Explicit checks for "." and ".." filenames in cleanup - Defense-in-depth: multiple validation layers * Fix post-rebase compilation errors - Remove duplicate NostrIdentityBridge and Bech32 from NostrIdentity.swift (now in separate files) - Add caching to NostrIdentityBridge.deriveIdentity() for performance - Remove duplicate NotificationStreamAssembler from BLEService.swift - Remove duplicate function declarations in BLEService.swift - Remove duplicate DeliveryStatusView and PaymentChipView from ContentView.swift - Fix PeerID type conversions throughout (use .id for String, PeerID(str:) for wrapping) - Update ContentView body to use main's simple VStack structure - Fix NostrIdentityBridge instance method calls - Remove privateChatView (replaced with sheet-based UI in main) Build and tests passing (137/139 tests pass). * Fix remaining compilation issues after rebase - Fix PhotosUI import order (must be after platform imports) - Fix Data.WritingOptions.atomic reference - Add identity derivation caching to NostrIdentityBridge - Fix all remaining PeerID type conversions in ChatViewModel - Fix ContentView body structure to use main's VStack layout - Fix PaymentChipView API usage (now uses PaymentType enum) Build and tests now passing. * Add proper availability checks for PhotosPickerItem PhotosPickerItem requires iOS 16+ / macOS 13+ but canImport(PhotosUI) succeeds on older macOS versions. Add compiler version check to ensure PhotosPicker code only compiles when actually available. This fixes CI build failures on older macOS environments. * Limit PhotosPicker to iOS only to fix CI PhotosPickerItem has SDK availability issues on macOS in CI. Change PhotosPicker from canImport(PhotosUI) to os(iOS) only. macOS users can still import images via file importer (.fileImporter). This is actually cleaner as macOS file picker is more familiar to users. Fixes CI build failures. * Convert new tests to Swift Testing * Fix compilation issue * Add the missing `fileTransfer` case * Explicitly list all Enum cases to get compile-time errors * Revive lost `NotificationStreamAssembler` changes * Allow file fragments to account for protocol overhead * Simplify PR: Focus on audio+image, fix EXIF stripping, remove file transfers - Fix critical EXIF privacy issue in iOS image processing - Both iOS and macOS now use CGImageDestination for metadata stripping - Shared encodeJPEG function ensures no GPS, camera, or metadata leaks - Remove file transfer functionality to simplify PR scope - Deleted FileAttachmentView - Removed sendFileAttachment from ChatViewModel - Removed file picker UI from ContentView - Simplified attachment dialog to image only (iOS) or voice only - Keep focused media features: - Voice recording and playback - Image sending with progressive reveal - Binary protocol for media transfer * Improve camera UX: Direct camera access with camera icon - Change paperclip icon to camera icon for clearer affordance - Open camera directly on tap (no confirmation dialog) - Add CameraPickerView wrapper for UIImagePickerController - Remove PhotosPicker in favor of direct camera access - Images still processed through ImageUtils with EXIF stripping - Accessibility: Added 'Take photo' label * Full-screen camera with photo library option - Change to fullScreenCover for immersive camera experience - Add action sheet with 'Take Photo' and 'Choose from Library' options - Renamed ImagePickerView to support both camera and library sources - Both options open full-screen for better UX - Updated accessibility label to 'Add photo' (more accurate) * Fix camera white bars with overFullScreen presentation - Changed modalPresentationStyle from .fullScreen to .overFullScreen - This should eliminate white bars at top/bottom on notched devices - Explicitly set showsCameraControls and cameraOverlayView for camera mode * Gesture-based photo access: Tap for library, long-press for camera UX improvements: - Tap camera icon → Photo library (common use case) - Long press camera icon (0.3s) → Direct camera (quick photos) - Removed action sheet entirely for cleaner flow - Power users can long-press for instant camera access This is more discoverable and eliminates an extra step in the UI. * Simplify camera presentation to reduce frame errors - Changed back to standard .fullScreen presentation - Removed overFullScreen which was causing frame dimension errors - Let iOS handle safe areas automatically (white bars are intentional) - Reduces gesture gate timeout warnings Note: White bars on notched devices are iOS default behavior for UIImagePickerController. This respects safe areas for status bar and home indicator. True edge-to-edge would require custom AVFoundation camera implementation. * Force dark mode on camera/picker for black safe area bars - Set overrideUserInterfaceStyle = .dark on UIImagePickerController - Changes white bars to black (much better looking) - Camera controls and photo library also appear in dark mode - Consistent dark appearance regardless of system settings * Optimize sheet presentation for camera UI - Force .large detent for maximum height - Hide drag indicator for cleaner look - Use ignoresSafeArea to give camera full space - Should show complete flash button and controls * Fix P1: Add DoS protections to PeerID fragment handler Critical security fix addressing Codex review feedback: The PeerID overload of handleFragment (which is actually called by CoreBluetooth) was missing key safety checks that existed in the String overload: 1. Added total <= 10000 check to prevent unbounded fragment counts 2. Added cumulative size check against FileTransferLimits before storing each fragment 3. Prevents memory exhaustion DoS attacks via malicious fragment streams This ensures the actually-used code path has proper bounds checking. * Fix decompression size limit to support max-sized file transfers Root cause: BinaryProtocol.decode() was rejecting decompressed payloads larger than maxPayloadBytes (1 MB), but TLV-encoded file transfers are slightly larger due to metadata overhead. Fixes: - Changed decompression limit from maxPayloadBytes to maxFramedFileBytes - This accounts for TLV overhead (~50 bytes) + binary protocol headers - Now allows ~1.12 MB decompressed payloads (1 MB + overhead budget) The failing test was: - Creating 1 MB file content - TLV encoding adds ~50 bytes (1,048,627 total) - Compression reduces to ~1,084 bytes (highly repetitive data) - During decode, decompression was rejecting the 1,048,627 byte output - Now correctly allows it since 1,048,627 < 1,179,760 (maxFramedFileBytes) All 154 tests now pass including 'Max-sized file transfer survives reassembly' * Fix critical thread-safety crash in PeerID fragment handler CRITICAL: The PeerID version of _handleFragment was accessing incomingFragments dictionary without collectionsQueue synchronization, causing crashes when multiple BLE threads processed fragments concurrently. Crash stack trace pointed to line 3431 (dictionary subscript) with: 'doesNotRecognizeSelector' - classic concurrent mutation crash. Fix: - Wrapped ALL incomingFragments/fragmentMetadata access in collectionsQueue.sync(flags: .barrier) - Matches the thread-safe pattern used in String version - Separate cleanup into its own barrier block after reassembly - Prevents concurrent dictionary mutations from multiple BLE threads This is the same pattern as the String version (line 1128) which didn't crash. * Remove Localizable.xcstrings formatting noise The Localizable.xcstrings file had massive formatting-only changes (spacing: 'key' vs 'key :') that added 50K+ lines to the PR diff. This was just Xcode reformatting with no actual string changes. Reverted to main's version to keep PR focused on actual code changes. * Add macOS photo picker support - Added MacImagePickerView with NSOpenPanel for macOS - macOS shows photo.circle.fill icon (no camera hardware) - Opens native file picker for images (.png, .jpeg, .heic) - Images processed through ImageUtils with EXIF stripping - Simple sheet with Select/Cancel buttons Cross-platform photo sharing now works: - iOS: Tap for library, long-press for camera - macOS: Tap for file picker * Add Localizable strings for camera and voice features Xcode auto-generated localization strings for new UI elements: - Camera/photo picker labels - Voice recording UI strings - Media attachment descriptions These are legitimate new strings needed for the audio+image feature, not just formatting changes. --------- Co-authored-by: jack <jackjackbits@users.noreply.github.com> Co-authored-by: islam <2553451+qalandarov@users.noreply.github.com> |
||
|
|
23249f3e41 |
PeerID 22/n: PrivateChatManager (#800)
* PrivateChatManager: functions to use `PeerID` * PrivateChatManager: properties to use `PeerID` |
||
|
|
10e3f574ab |
PeerID 16/n: BitchatPeer / PeerInfo / TransportPeer (#755)
* PeerID 15/n: Bitchat Message & Packet accept in init * PeerID 16/n: BitchatPeer / PeerInfo / TransportPeer |
||
|
|
5f44c56a90 | PeerID 15/n: Bitchat Message & Packet accept in init (#754) | ||
|
|
64f91bb1d6 |
PeerID 10/n: MessageRouter (#749)
* PeerID 9/n: `NoiseEncryptionService` * PeerID 10/n: `MessageRouter` |
||
|
|
8cfee095d3 | PeerID 6/n: Unifiy validation (#743) | ||
|
|
90273cee2d |
PeerID 4/n: BitchatMessage.senderPeerID + String equality (#739)
|
||
|
|
8d938e8518 |
PeerID 2/n: Count and hex conversion done with bare (#705)
|
||
|
|
bf712a0610 |
Refactor peerID - 1/n: Add PeerID + Tests (#688)
* Unify SHA256 hash and hex usages * Refactor `peerID` - 1/n: Add `PeerID` + Tests |
||
|
|
6a6504c6f2 |
Refactor: Extract types from BitchatProtocol (#611)
* Extract BitchatMessage into a separate file * Convert `fromBinaryPayload` to `convenience init?` * Extract message dedup into an extension * Remove dead `formatMessageContent` * Minor refactor of timestamp and username formatting * Remove dead `getSenderColor` * Extract MessagePadding into a separate file * Extract BitchatPacket into a separate file * Extract ReadReceipt into a separate file * Extract NoisePayload into a separate file * Remove unnecessary import |
||
|
|
ea8d51a36b |
Refactor: BitchatMessage (#610)
* Extract BitchatMessage into a separate file * Convert `fromBinaryPayload` to `convenience init?` * Extract message dedup into an extension * Remove dead `formatMessageContent` * Minor refactor of timestamp and username formatting * Remove dead `getSenderColor` |
||
|
|
2ac01db9c4 |
SYNC_REQUEST 2 (#616)
* wip * woohooo * Plumtree gossip: don't subset REQUEST_SYNC fanout; make RequestSyncPacket.encode use const * bloom -> gcs [wip] * fix build * fix broadcast * prune old messages too * faster sync * prune better * adjust parameters * fix(sync): make cap a constant in GCSFilter.buildFilter to silence 'never mutated' warning * fix(mesh): surface self-origin public messages recovered via sync; only ignore self when TTL != 0 in handleMessage * sync: allow self messages via GCS restore and relax TTL==0 acceptance\n- Bypass dedup for self TTL==0 packets in handleReceivedPacket\n- Accept self TTL==0 in handleMessage and set nickname\n- Accept unknown senders for TTL==0 with anon# prefix to restore history --------- Co-authored-by: jack <jackjackbits@users.noreply.github.com> |
||
|
|
96c6fc0c0d |
Improve mesh relaying, presence, and DM robustness; signed public msgs; reachability UI; quicker announces; store-and-forward (#527)
* Sign public broadcasts; verify relayed messages via persisted signing keys; keep scheduled relays in sparse graphs and speed their jitter; persist announce signing key for offline auth; add short backoff after disconnect errors to reduce reconnect thrash * Add connected vs reachable model: retain peers after link drop, expire after reachability window; expose all peers in snapshots; compute isReachable in UI; add meshReachable state and sorting; avoid removing peers on link events; notify UI on stale removals * ContentView: handle new .meshReachable connection state in header icon switch (exhaustive switch fix) * Logs: tag relayed announces as 'Reachable via mesh' and annotate public message logs with (direct|mesh) path for easier field analysis * Fix syntax error: remove stray else/log inserted into writeOrEnqueue; keep logs clean * UI: use 'point.3.connected.trianglepath.dotted' for mesh-reachable; change people count to include connected+reachable (exclude Nostr-only) * UI: switch to 'point.3.filled.connected.trianglepath.dotted' for mesh-reachable icons in list and header * Reachability: reduce retention to 21s for all peers (verified and unverified) to minimize stale presence * mesh DMs/acks: route to reachable peers; queue READ/DELIVERED until handshake; add Transport.isPeerReachable; UI: hide offline non-mutuals; DM header: better name fallback + show transport + encryption icons; fix NostrTransport conformance * Verification sheet: compute encryption status and fingerprint using short mesh ID mapping (fix 'not encrypted/handshake' for DMs with stable key) * Announce cadence: faster discovery (4s), sparse 15±4s, dense 30±8s; initial 0.6s; post-subscribe 50ms; min-force 150ms; maintenance 5s; proactive announces on handshake + recent-traffic nudge * Relay: increase broadcast TTL cap in sparse graphs to 6; tighten jitter for handshake (10–35ms) and directed (20–60ms) relays * Range/robustness: store-and-forward for directed packets (15s) with flush on new links + periodic; announces: no subset + afterglow re-announce on first-seen; adaptive scanning: force ON when <=2 neighbors or recent traffic * Fix warnings: remove unused msgID and unused mutable var in directed spool flush * Announces: TTL 7 (sparse only) via RelayController; no fanout subset for announces; neighbor-change rebroadcast of last 2–3 announces. Fragments: faster pacing (5ms global, 4ms directed). * Peer list: real-time icon updates by publishing snapshots on connectivity checks; add unread message indicator (envelope) next to peers with unread DMs * UI: unread envelope uses orange; hasUnreadMessages checks Nostr conv key for peers with known Nostr pubkeys (geohash DM consistency) * Logs/robustness: debounce disconnect notifications (1.5s), debounce 'reconnected' logs (2s), add weak-link cooldown after timeouts on very weak RSSI (<= -90) * Peer icons: faster, accurate reachability\n\n- Run connectivity checks every maintenance tick (5s)\n- Publish peer snapshots on central unsubscribe for instant UI refresh\n- Lower inactivity timeout to 8s and disconnect debounce to 0.9s\n- Gate reachability on mesh-attached (>=1 direct link); no links => no reachable peers\n- Keep 21s retention for verified/unverified, but only when attached to mesh\n\nImproves list responsiveness when walking out of range and prevents stale 'reachable' states when isolated. --------- Co-authored-by: jack <jackjackbits@users.noreply.github.com> |
||
|
|
60b0deee7b |
Cleanup: remove dead code, normalize fingerprints, modernize share extension, trim test noise, and drop ‘preparing to share…’ message (#520)
* Remove dead code and artifacts: drop PeerManager, unused views/types; delete LegacyTestProtocolTypes; update .gitignore; purge TestResult.xcresult and build.log * Tests: gate verbose prints under DEBUG; ChatViewModel: remove legacy fingerprint helper and rely on UnifiedPeerService * Share Extension: migrate to UIKit + UTTypes; drop Social/SLComposeServiceViewController * Remove 'preparing to share …' system message; send shared content immediately * Inline comment cleanup: drop legacy 'removed' breadcrumbs across protocols, services, view model, and views --------- Co-authored-by: jack <jackjackbits@users.noreply.github.com> |
||
|
|
6fbf7eee25 |
Refactor/ble nostr boundaries (#449)
* Refactor: move Nostr embedding and TLVs out of BLE; add NostrEmbeddedBitChat, Packets, PeerIDUtils; centralize MessageDeduplicator; update ChatViewModel to use new helpers; remove AI_CONTEXT.md and CLAUDE.md * Rename class to BLEService with compatibility alias; move mention parsing out of BLE; emit low-level BLE events to delegate; unify hex helpers; accept 64-hex in isPeerConnected; add PeerIDResolver * Project: rename file to BLEService.swift and update Xcode project; keep typealias SimplifiedBluetoothService = BLEService for compatibility * Remove SimplifiedBluetoothService alias; update app code to use BLEService explicitly * Tests: rename MockSimplifiedBluetoothService to MockBLEService; update typealiases and Xcode project * Docs: update comments to refer to BLEService (tests, protocol, noise service) * Tests: rename SimplifiedBluetoothServiceTests to BLEServiceTests; update project references and class names * Introduce Transport protocol; BLEService conforms; document delegate-only event pattern in BLEService; keep publishers internal for UnifiedPeerService * Adopt Transport end-to-end: add TransportPeerSnapshot + publishers; BLEService maps to Transport snapshots; UnifiedPeerService consumes Transport; ChatViewModel holds Transport * Fix Transport integration: replace getPeerFingerprint with getFingerprint(for:); update PrivateChatManager and CommandProcessor to use Transport; add BLEService.getFingerprint(for:); update PeerManager to use Transport * Refactor transport and BLE/Nostr layers; unify UI events; fix MainActor isolation - Rename SimplifiedBluetoothService to BLEService and slim responsibilities - Introduce Transport protocol and peerEventsDelegate for UI updates - Add NostrTransport and MessageRouter to route PM/read/favorite via BLE or Nostr - Centralize TLVs, PeerID utils, and MessageDeduplicator outside BLE - Update UnifiedPeerService and ChatViewModel to use Transport and delegate events - Fix MainActor isolation: route delegate calls via Task on MainActor; update notifyUI helper - Adjust related files and tests accordingly * BLEService: remove internal publishers; switch to delegate-only events - Drop legacy messages/peers/fullPeers publishers - Provide lightweight peerSnapshotSubject only to satisfy Transport - Rework publishFullPeerData to build snapshots from internal state and notify delegate + subject - Remove all peersPublisher.send call sites - Keep UnifiedPeerService on delegate updates exclusively * Remove inlined Nostr send helpers from ChatViewModel; route via MessageRouter - Replace direct Nostr sends (PM, ACKs, favorites) with MessageRouter - Add router method for delivery ACKs and implement NostrTransport.sendDeliveryAck - Simplify ChatViewModel favorite notification path to use router - Keep Nostr receive handling intact; reduce duplication * Fix ReadReceipt initializer usage in ChatViewModel (readerID + readerNickname) * Fix unused variable warning: replace shadowed 'nostrPubkey' bind with boolean check in ChatViewModel * Fix queued PM format: use TLV for pending messages after Noise handshake - Pending messages (including first-time favorite notifications) now use the same TLV encoding as normal sends - Ensures ChatViewModel can decode on first send, even if handshake completes after queuing --------- Co-authored-by: jack <jackjackbits@users.noreply.github.com> |
||
|
|
3ebfa85e90 |
Feature/nostr embedded bitchat (#448)
* UI: prefer mesh radio icon when connected; map short peer ID to full Noise key; rename ephemeral mapping to shortIDToNoiseKey; ensure header flips to purple globe on disconnect and keeps name. * chore: stop tracking build artifacts; ignore .DerivedData and .Result* * logging: add global threshold via BITCHAT_LOG_LEVEL and demote chatty logs to debug; keep critical errors/warnings and key state transitions --------- Co-authored-by: jack <jackjackbits@users.noreply.github.com> |
||
|
|
845ffc601b |
Refactor/robustness (#446)
* Refactor BitChat for improved robustness and performance Major refactoring to simplify architecture and fix critical issues: Architecture Improvements: - Replace complex BluetoothMeshService with simplified SimplifiedBluetoothService - Consolidate message routing and peer management into unified services - Remove redundant caching layers and optimize performance Bug Fixes: - Fix critical BLE peer mapping corruption in mesh networks - Fix encrypted message routing failures in multi-peer scenarios - Fix app freezes and Main Thread Checker warnings - Fix BLE message delivery in dual-role connections - Fix favorite toggle UI not updating instantly - Fix Nostr offline messaging with 24-hour message filtering Features: - Add command processor for chat commands - Add autocomplete service for mentions and commands - Improve private chat management with dedicated service - Add unified peer service for consistent state management Performance: - Optimize BLE reconnection speed - Reduce excessive logging throughout codebase - Improve message deduplication efficiency - Optimize UI updates and state management * Improvements refactor robust (#441) * remove unused code * remove more * TLV for announcement * restore * restore * restore? * messages tlv too (#442) * Fix Nostr notification and read receipt issues, add TLV encoding, cleanup unused code ## Notification & Read Receipt Fixes - Fixed toolbar notification icon appearing incorrectly on app restart for already-read messages - Fixed read receipts being incorrectly deleted on startup when privateChats was empty - Fixed messages not being marked as read when opening chat for first time - Fixed senderPeerID not being updated during message consolidation - Added startup phase logic to block old messages (>30s) while allowing recent ones - Fixed unread status checking across all storage locations (ephemeral, stable Noise keys, temporary Nostr IDs) ## TLV Encoding Implementation - Implemented Type-Length-Value encoding for private message payloads - Added PrivateMessagePacket struct with TLV encode/decode methods - Enhanced message structure for better extensibility and robustness ## Code Cleanup - Removed unused PeerStateManager class and related dependencies - Removed dead protocol types (DeliveryAck, ProtocolAck/Nack, NoiseIdentityAnnouncement) - Cleaned up BitchatDelegate by removing unused methods - Removed excessive debug logging throughout ChatViewModel - Added test-only ProtocolNack helper for integration tests ## Technical Details - Messages stored under three ID types: ephemeral peer IDs, stable Noise key hexes, temporary Nostr IDs - Fixed cleanupOldReadReceipts() to skip when privateChats is empty or during startup - Updated message consolidation to properly update senderPeerID - Restored NIP-17 timestamp randomization (±15 minutes) for privacy --------- Co-authored-by: jack <jackjackbits@users.noreply.github.com> Co-authored-by: callebtc <93376500+callebtc@users.noreply.github.com> |
||
|
|
f4b8168ef9 |
Remove dead code and simplify codebase
- Remove unused BinaryEncodable protocol and BinaryMessageType enum - Delete MockNoiseSession.swift (never used in tests) - Remove all relay detection code (hardcoded to false) - Removed isRelayConnected property from BitchatPeer - Removed relayConnected case from ConnectionState enum - Cleaned up relay-related UI indicators in ContentView - Removed relay status checks from ChatViewModel - Simplified peer connection logic by removing relay layer Total: 169 lines removed across 5 files |
||
|
|
3226b9cd14 |
Optimize logging to reduce verbosity while preserving critical network events (#432)
- Remove excessive verbose logging (DISCOVERY, INCOMING, PEER-UPDATE, etc.) - Preserve critical network state logs (RESTORE, handshake failures, security events) - Change routine key operations from info to debug level - Add successful peer connection log after handshake completion - Fix compiler warnings for unused variables - Achieve ~95% reduction in log volume while maintaining debugging capability Co-authored-by: jack <jackjackbits@users.noreply.github.com> |
||
|
|
d605a0b6db |
Fix crash from duplicate peer IDs in Dictionary initialization
- Added deduplication logic in ChatViewModel to handle duplicate peer IDs gracefully - Enhanced PeerManager to prevent duplicate peers by tracking both nicknames and IDs - Added final safety check to ensure no duplicates in peers array - This fixes crash when Dictionary(uniqueKeysWithValues:) encounters duplicate keys |
||
|
|
a97d5c2d5e |
Implement Nostr NIP-17 for offline messaging and performance optimizations (#358)
* Implement Nostr NIP-17 integration for offline mutual favorite messaging - Add Nostr relay connectivity and NIP-17 gift-wrapped private messages - Implement dual transport system: Bluetooth mesh + Nostr relays - Add favorites persistence with mutual detection and Nostr key exchange - Support offline messaging for mutual favorites via Nostr relays - Handle peer identity rotation with automatic favorite key updates - Fix UI to show all favorites (online and offline) in peer list - Add proper message routing based on peer availability - Update peer list icons: 📶 for mesh, 🌐 for Nostr, 🌙 for one-sided - Fix toolbar display for offline peers in private chat view - Add network entitlements for macOS and iOS - Implement automatic noise key updates when peers reconnect * Implement Nostr NIP-17 for private messaging between mutual favorites - Add support for NIP-17 gift-wrapped private messages with double encryption - Enable private messaging via Nostr when mutual favorites are offline - Fix peer reconnection issues: users now stay in private chat when peer reconnects - Fix read receipt delivery: send pending receipts when peer comes back online - Add message ID tracking through Nostr transport for proper delivery acknowledgments - Update peer noise key mapping when peers reconnect with different IDs - Check for Nostr messages when app becomes active - Implement 7-day message retrieval window for better reliability * Fix private message UI refresh and adjust PEOPLE header spacing - Fix UI not updating when receiving private messages on mesh - Add immediate batch processing for messages in active chat - Force UI update when viewing current chat peer - Ensure real-time message display without navigation - Reduce PEOPLE header spacing from 16 to 12 points for tighter UI * Fix build errors and unused value warnings in Nostr favorites integration * Implement read receipts via Nostr - Added sendReadReceipt method to MessageRouter to send receipts via mesh or Nostr - Added handleReadReceipt to process incoming read receipts from Nostr - Made ReadReceipt.readerID mutable to allow updates - Added missing notification names and error cases - Uncommented and enabled read receipt handling in ChatViewModel - Read receipts now work seamlessly via both mesh and Nostr transports * Fix ReadReceipt initialization - use correct constructor * Implement persistent message deduplication for Nostr - Added ProcessedMessagesService to track messages across app restarts - Store processed message IDs and last timestamp in UserDefaults - Skip already processed messages when receiving from Nostr - Adjust subscription filter to use smart timestamp (last processed or 24h) - Prevents duplicate messages when reconnecting to Nostr relays * Fix peer list UI not updating to Nostr mode on disconnect - Remove peer from peerNicknames when connection state changes to disconnected - Ensures UI properly reflects peer disconnection state - Peer list now correctly shows Nostr mode (🌐) when peer walks out of range * Update peer count to include Nostr peers and improve UI indicators - Peer count now shows total peers including those available via Nostr - Count appears purple when only Nostr peers are connected - Private message header shows purple globe icon for Nostr transport - Consistent visual language for Nostr connectivity across the app * Improve RSSI real-time updates and fix UI flashing - Reduce RSSI update timer from 10s to 5s and per-peripheral from 5s to 3s - Add RSSI change detection with 2 dBm threshold for responsive updates - Always update previous RSSI values to fix gradual change detection bug - Trigger RSSI read on peer authentication for immediate status - Fix UI flashing 'nobody around' by removing array clearing on updates - Add proper cleanup of RSSI tracking on disconnect and peer rotation * Fix favorite nickname updates and peer list filtering - Add updateNickname method to FavoritesPersistenceService to update nicknames while preserving favorite status - Update announce handler to check for existing favorites and update their nicknames - Remove dead BluetoothMeshService+PublicAPI.swift file - Move sendFavoriteNotification to main BluetoothMeshService - Fix peer list to only show connected peers and user's favorites (not peers who favorite the user) - Remove UI logic for showing peers who favorite us but we don't favorite back * Remove dead code and fix ghost connections Phase 1 - Remove abandoned peer ID rotation code: - Remove previousPeerID property and rotationGracePeriod constant - Remove grace period logic from isPeerIDOurs() - Remove previousPeerID handling from announce packets - Pass nil for previousPeerID in identity announcements Phase 2 - Fix ghost connections from relayed packets: - CRITICAL FIX: Only add peers to activePeers if they have a peripheral connection - Check for peripheral connection before marking peer as active - Prevents ghost connections when announce packets are relayed - Log warning when rejecting relayed announce without peripheral Phase 3 - Begin consolidating redundant peer tracking: - Create new PeerSession class to unify peer data in one place - Add helper methods for PeerSession management - Integrate PeerSession into announce packet handling - Update authentication state changes to use PeerSession - Update peripheral mapping and RSSI to sync with PeerSession - Update disconnect and leave handling to update PeerSession - Add consolidated getter methods for peer info This fixes the issue where peers appeared connected without actually having a Bluetooth connection, and begins the migration to a cleaner single-source-of-truth peer tracking system. * Fix multiple connect messages on peer restart - Move hasPeripheralConnection check outside sync block to fix scope issue - Add debug logging to track connect message conditions - Ensure connect messages only show on first connection or reconnection with peripheral * Optimize RSSI updates for better battery life - Add app state tracking to BluetoothMeshService - Only update RSSI when app is in foreground and peer list is visible - Add setPeerListVisible method to control RSSI updates - Remove individual periodic RSSI updates in favor of centralized timer - Update ContentView to notify mesh service of peer list visibility changes - Improve battery efficiency by avoiding unnecessary RSSI reads * Initialize peer list visibility state on view appear - Ensure RSSI timer state is properly initialized when view loads - Call setPeerListVisible with initial showSidebar value * Fix duplicate peers and multiple disconnect messages - Fixed duplicate peer entries when relay-connected by adding relay-connected peers to connectedNicknames set - Added deduplication logic for disconnect messages with 2-second window to prevent multiple disconnect notifications for same peer - Added cleanup for old disconnect notification tracking to prevent memory growth * Fix peer count indicator color logic - Show green for any mesh peer (direct Bluetooth or relay connected) - Show purple only for Nostr-only peers (no mesh connections) - Show red only when no peers are reachable at all - Fixed to use meshPeerCount instead of viewModel.isConnected which only checked direct connections * Fix relay connection issues and peripheral mapping cleanup - Fixed relay-connected peers being marked as directly connected when receiving identity announce - Added proper cleanup of temp peripheral mappings when discovering real peer ID - Fixed disconnect notification deduplication cleanup - Improved debug logging to show actual connection state (direct/relay/nostr/offline) - Added debug logging for relay connection detection - Fixed compiler warning about unused variable * Fix Unknown peer disconnect notifications and disable faulty relay detection - Add check to prevent disconnect notifications for Unknown peers that never announced - Disable relay connection detection until proper relay tracking is implemented - In a 2-peer network, peers should never show as relay-connected * Fix RSSI updates and peer visibility after reconnection - Add updatePeers() call in didUpdatePeerList to refresh RSSI values in UI - Track version hello times to better detect direct connections - Allow peers to be marked active if recent version hello received - Fix thread safety for version hello tracking - Clean up old version hello times to prevent memory leaks * Remove RSSI tracking completely and replace with radio icon for mesh connections * Fix build errors after RSSI removal - Add missing peripheralID declaration in didDiscover delegate method - Remove obsolete setPeerListVisible calls from ContentView * Center private message header elements using ZStack layout - Replace HStack with ZStack for perfect centering - Globe/nick/lock cluster now always centered regardless of button sizes - Back and favorite buttons positioned in overlay HStack * Fix private chat view showing Unknown when peer reconnects with new ID - Update FavoritesPersistenceService to notify with both old and new keys - Handle peer ID changes in ChatViewModel to migrate private chat data - Update selectedPrivateChatPeer when favorite's noise key changes - Maintain chat history and unread status across peer ID changes * Fix read receipts after peer reconnection and replace nos.lol relay - Updated sendReadReceipt to resolve current peer ID when peers reconnect with new IDs - Enhanced MessageRouter to check favorites for current noise keys - Replaced nos.lol relay with relay.snort.social to avoid PoW requirements * Fix message routing to use Nostr when peers are disconnected Changed message routing logic to check actual peer connection status using isPeerConnected() instead of just checking if peer exists in nickname list. This ensures that offline mutual favorites correctly route messages through Nostr instead of attempting Bluetooth handshakes. Also added safety check to prevent starting private chat with ourselves. * Add debug logging for Nostr timestamp randomization Added logging to track the random offset being applied to Nostr event timestamps to debug why messages appear 8-9 minutes in the future. * Fix Nostr timestamp issue by reducing randomization range Temporarily reduced the timestamp randomization from +/-15 minutes to +/-1 minute to address messages appearing 8-9 minutes in the future. Added detailed UTC/local time logging to help debug the issue. The random offset should have been evenly distributed but was consistently showing positive offsets. This change mitigates the issue while we investigate the root cause. * Fix message routing for offline favorites and reduce Nostr timestamp randomization - Fix transport selection to properly detect disconnected peers using isPeerConnected() - Change from checking peer nicknames to checking actual connection status - Reduce Nostr timestamp randomization from ±15 minutes to ±1 minute - Add detailed timestamp logging for debugging * Improve PM header UI and encryption status display - Show transport icons (radio/link/globe) in PM header matching peer list - Always show lock icon if noise session ever established (no handshake icon) - Change verified icon from shield to checkmark seal - Use consistent green color (textColor) for PM header and encryption icons * Update AI_CONTEXT.md with comprehensive Nostr implementation details - Add Nostr and MessageRouter to architecture diagram - Document NIP-17 gift wrap implementation - Explain favorites integration and mutual requirement - Detail message routing logic and transport selection - Add security considerations and debugging tips - Update common tasks with Nostr-specific guidance * Fix data consistency issues in favorites, chat migration, and bloom filter - Fix favorites deduplication to use public key instead of nickname Prevents losing favorites when multiple peers use same nickname - Fix private chat migration to use fingerprints instead of nicknames Prevents merging unrelated conversations that share nicknames Fallback to nickname matching only for legacy data without fingerprints - Fix bloom filter reset to preserve messages from last 10 minutes Prevents duplicate message processing after bloom filter resets Keeps processedMessages for 10 minutes while bloom filter resets every 5 * Add mutual favorites internet messaging to app info * Remove excessive debug/info logging for production readiness - Removed ~140 debug/info level logs across core services - Preserved critical logs: errors, warnings, security events, state changes - Kept logs for: peer join/leave, favorite status, mutual relationships - Cleaned up verbose logging in: Bluetooth mesh, Nostr, message routing - Improved performance by reducing log I/O overhead 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com> * Implement performance optimizations and fix build warnings - Add UI update debouncing (50ms) to prevent excessive SwiftUI refreshes - Implement memory bounds for processedMessages with LRU eviction - Add encryption queue cleanup for disconnected peers - Optimize peer lookups from O(n) to O(1) with indexed dictionary - Fix multiple compiler warnings (unused variables, missing break statements) - Optimize peer counting with single-pass reduce operation - Fix ViewBuilder control flow issue in ContentView - Fix Dictionary initialization type mismatches with Array wrapper * Add TTL-based cleanup for Noise handshake sessions - Add session TTL (5 minutes) and max session limit (50) to NoiseHandshakeCoordinator - Clean up old established sessions to prevent unbounded memory growth - Move handshake cleanup timer out of DEBUG conditional for production use - Run cleanup every 60 seconds in production (vs 30s in debug) - Clean up crypto state immediately on peer disconnect - Prevents memory leaks from accumulating Noise sessions * Pre-compute and store fingerprints in PeerSession for O(1) lookups - Store fingerprint in PeerSession when peer authenticates - Update getPeerFingerprint() and getFingerprint() to check PeerSession first - Replace all noiseService.getPeerFingerprint() calls with optimized version - Eliminates repeated SHA256 calculations during message processing - Improves performance for favorite checks and encryption status updates * Implement exponential backoff for Nostr relay connections - Add reconnection tracking fields to Relay struct (attempts, timing) - Replace fixed 5-second delay with exponential backoff (1s → 2s → 4s... max 5min) - Stop reconnection attempts after 10 failures to prevent infinite retries - Reset attempt counter on successful connection - Add utility methods: retryConnection(), getRelayStatuses(), resetAllConnections() - DNS failures still bypass retry logic as before - Improves battery life and reduces server load from constant reconnection attempts --------- Co-authored-by: jack <jackjackbits@users.noreply.github.com> Co-authored-by: Claude <noreply@anthropic.com> |