Files
simon b06a279448 Add HUB_DEV_NOTES.md for upstream Hub merge guidance
Documents required electron/src/call.ts dual-ring fix and
presence_bridge.py voice/audio changes for the Hub developer,
plus optional mobile-only context and test steps.
2026-06-11 20:00:37 +00:00

181 lines
8.3 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Hub developer notes — Qortal GO 2.0 upstream merge
This document lists changes in the `simon/qortal-go-2.0` fork that **must be ported into upstream Qortal Hub** (Electron) for voice calls and multi-device ringing to work correctly with Qortal GO on Android.
Mobile-only changes (Capacitor UI, FileWriter, biometric, etc.) do **not** require Hub merges unless noted.
---
## Repo state reference
**Remote:** https://gitea.qortal.link/simon/qortal-go-2.0 (`simon/main`)
**Already pushed (before the pending voice/QDN commit):**
| Commit | Summary |
|------------|---------|
| `0a475a37` | Reticulum presence reliability on Android (announce retry, native tick, Python re-fanout) |
| `d4ca8928` | Biometric unlock, transfer notifications, mobile UI polish |
| `14244429` | Initial Qortal GO mobilization (Android shell, Reticulum file transfer) |
**Pending in the next push (after `0a475a37`):** voice-call fixes, QDN mobile file save, dual-ring Hub fix, node settings fix — see commit message on `simon/main` after you run the push script.
---
## 1. Required — dual-device ringing
### Problem
When the same Qortal account is logged in on **Hub (desktop)** and **Qortal GO (mobile)**, an incoming voice call often rang on **Hub only**. Hub received the overlay `CALL_REQUEST` wire, saw the target address as local, and **stopped relaying** — so the mobile device never saw the call.
### Fix
In `electron/src/call.ts`, method `onReticulumCallWire`:
- **Always** decrement `L` (hops remaining) and relay the wire while hops remain, **even when** `targetIsLocal === true`.
- Loop prevention is unchanged: overlay dedupe id `X` via `hasSeenReticulumOverlayId` / `rememberReticulumOverlayId`.
- Only skip further local processing when the target is not local (existing behaviour after relay).
### File
`electron/src/call.ts` (~lines 788805 in the GO fork)
### Diff shape (conceptual)
```typescript
// BEFORE: relay only when target is NOT local
if (!targetIsLocal) {
if (overlayMeta.hopsRemaining > 0) {
this.broadcastReticulumOverlayWire(forwarded, [peerPresenceHash]);
}
...
}
// AFTER: relay first whenever hops remain; then handle local vs remote
if (overlayMeta.hopsRemaining > 0) {
this.broadcastReticulumOverlayWire(forwarded, [peerPresenceHash]);
}
if (!targetIsLocal) {
...
}
```
### Test
1. Log in as account **A** on Hub **and** on GO (same account, two devices).
2. From account **B**, start a voice call to **A**.
3. **Expected:** incoming call UI on **both** Hub and mobile within a few seconds.
4. Accept on one device → the other should stop ringing (GO implements “answered on another device”; Hub may still show its own accept path).
---
## 2. Required — `presence_bridge.py` (Electron bundle)
The GO fork keeps **`electron/resources/presence_bridge.py`** in sync with **`android/app/src/main/python/presence_bridge.py`**. Upstream Hubs Python bridge path may differ; port the **same logic** into wherever Hub loads its bridge.
### 2a. Inbound audio dropped — `UnboundLocalError`
**Function:** `_emit_group_audio_packet_json`
**Bug:** `_audio_json_egress_drops` was incremented without a `global` declaration → Python treated it as function-local → **first inbound audio frame raised `UnboundLocalError`** → callee heard nothing.
**Fix:**
```python
global _mobile_audio_packet_emits, _audio_json_egress_drops
```
### 2b. Overlay link misclassified as non-audio
**Problem:** If the **first** packet on an inbound Reticulum link was lost, the bridges classify fallback marked the link as **overlay**. Subsequent call-audio packets on that link were **silently dropped** for the rest of the call.
**Fix:**
- Add `_promote_misclassified_overlay_link_to_audio(link, overlay_link_id)`.
- In `on_overlay_link_packet`, if the link is incoming and the payload decodes as group audio wire, promote to audio link and delegate to `on_audio_link_packet`.
### 2c. Outbound audio send failures (`unknown_link_id` / `audio_link_not_ready`)
**Problem:** The JS layer held a **stale `linkId`** while the peer churned links (common mobile↔mobile). The bridge rejected thousands of outbound frames.
**Fix:**
- Add `_find_established_audio_link_locked(peer_key_hint)` — pick the most recently active **established** link matching presence **or** destination hash.
- In `_snapshot_audio_link_for_send`, when the requested link is missing or not established, **fall back** to that helper before returning not-ready / unknown.
### 2d. Unbounded link-establish retry loop
**Problem:** Stale peer destinations from a **previous call** never established; the bridge retried forever (`attempt=16`, `17`, … in logs), starving the scheduler.
**Fix:**
- `_AUDIO_LINK_MAX_ESTABLISH_ATTEMPTS = 10`
- In `_schedule_audio_link_retry`, if `attempts >= max`, set `desired = False` and log `audio_link_retry_gave_up`.
- On `_open_group_audio_link_for_peer` with `retry_reason == "command"`, reset `attempts = 0` (new call / explicit open).
- On `on_outgoing_audio_link_established`, reset `attempts = 0`.
### Test (Hub ↔ mobile voice)
1. Hub calls GO → both sides “in call”, **audio both ways**.
2. GO calls Hub → same.
3. GO ↔ GO (via Hub as relay) → media setup completes (not stuck on “Connecting”).
4. Bridge logs: no `UnboundLocalError` in `_emit_group_audio_packet_json`; no endless `audio_link_opening attempt=16+` after hang-up.
---
## 3. Optional / reference — mobile JS (Capacitor bridge)
These files are **Android / Capacitor** only. Upstream Hub uses `electron/src/group-call.ts` instead. Listed for context if you align behaviour:
| Area | File(s) | What it does |
|------|---------|--------------|
| Overlay relay meta on media wires | `src/bridge/capacitorBridge.ts` | Attach `X` (dedupe) + `L` (hops) to **all** `fanoutGroupCall` messages (GJ/GK/GQ), not just call signaling — required when two mobiles are not direct overlay neighbours |
| Stale peer destination between calls | `src/bridge/capacitorBridge.ts` | DM room id is stable; peer **audio destination rotates** per session — clear stale dest/link on join; adopt authoritative sender dest from inbound frames |
| In-call UI | `src/components/Mobile/MobileVoiceCallBar.tsx`, `src/App.tsx` | Persistent mobile call bar (Hub already has nav call pill) |
| Audio surface races | `src/bridge/mobileAudioSurface.ts`, `src/hooks/useVoiceCall.ts` | Serialize audio-surface commands; retry after “session disposed”; dismiss ring when accept seen on sibling device |
| QDN file save | `src/plugins/FileWriter.ts`, `src/utils/mobileFileSave.ts`, … | Native Documents save — **no Hub change** |
| Node settings display | `src/components/Mobile/MobileNodeConnectionSettings.tsx` | Use `isPublicGatewayNodeUrl` so local Core shows as private — **no Hub change** |
---
## 4. Presence reliability (commit `0a475a37`)
Mostly Android + shared JS:
- `src/hooks/usePresence.ts` — announce retry, heartbeat failure → re-announce
- `src/bridge/capacitorBridge.ts` — await `publish` success
- Native: `QortalForegroundService` presence tick → `QortalBackgroundPlugin`
- Python: periodic re-fanout of last signed presence wire (20s)
**Hub:** Python re-fanout in `electron/resources/presence_bridge.py` should match Android if Hub uses the same bridge bundle. Electron `usePresence` changes apply if Hub shares that hook (it does in this monorepo).
---
## Suggested merge order
1. **`electron/src/call.ts`** — dual-ring relay (Section 1).
2. **`electron/resources/presence_bridge.py`** — Sections 2a2d (diff from `0a475a37` to HEAD on `simon/main`).
3. Rebuild Hub; run Section 1 and 2 tests.
4. Optionally review `usePresence.ts` / presence_bridge re-fanout from `0a475a37` if mobile still shows “online on Hub but not vice versa” after voice fixes.
---
## Quick diff commands (from a clone of GO fork)
```bash
# Dual-ring only
git show simon/main:electron/src/call.ts # or
git diff 0a475a37..simon/main -- electron/src/call.ts
# Python bridge voice fixes
git diff 0a475a37..simon/main -- electron/resources/presence_bridge.py
```
---
## Contact / context
These notes correspond to Qortal GO 2.0 field testing: mobile↔Hub voice (working after bridge + call.ts fixes), mobile↔mobile (requires overlay meta + stale-dest rotation on GO side), dual-device ringing (requires Section 1 on Hub).
Update this file when new Hub-facing changes land on `simon/main`.