b06a279448
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.
181 lines
8.3 KiB
Markdown
181 lines
8.3 KiB
Markdown
# 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 788–805 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 Hub’s 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 bridge’s 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 2a–2d (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`.
|