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.
8.3 KiB
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 whentargetIsLocal === true. - Loop prevention is unchanged: overlay dedupe id
XviahasSeenReticulumOverlayId/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)
// 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
- Log in as account A on Hub and on GO (same account, two devices).
- From account B, start a voice call to A.
- Expected: incoming call UI on both Hub and mobile within a few seconds.
- 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:
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 toon_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, ifattempts >= max, setdesired = Falseand logaudio_link_retry_gave_up. - On
_open_group_audio_link_for_peerwithretry_reason == "command", resetattempts = 0(new call / explicit open). - On
on_outgoing_audio_link_established, resetattempts = 0.
Test (Hub ↔ mobile voice)
- Hub calls GO → both sides “in call”, audio both ways.
- GO calls Hub → same.
- GO ↔ GO (via Hub as relay) → media setup completes (not stuck on “Connecting”).
- Bridge logs: no
UnboundLocalErrorin_emit_group_audio_packet_json; no endlessaudio_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-announcesrc/bridge/capacitorBridge.ts— awaitpublishsuccess- Native:
QortalForegroundServicepresence 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
electron/src/call.ts— dual-ring relay (Section 1).electron/resources/presence_bridge.py— Sections 2a–2d (diff from0a475a37to HEAD onsimon/main).- Rebuild Hub; run Section 1 and 2 tests.
- Optionally review
usePresence.ts/ presence_bridge re-fanout from0a475a37if mobile still shows “online on Hub but not vice versa” after voice fixes.
Quick diff commands (from a clone of GO fork)
# 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.