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
2026-05-25 21:44:32 +03:00
2026-05-25 17:04:34 +03:00
2025-06-07 17:35:00 +02:00
2025-12-12 19:26:58 +02:00
2025-04-10 10:05:50 +02:00
2025-04-11 23:45:30 +02:00
2025-06-14 08:18:28 +02:00
2025-12-12 19:26:58 +02:00
2025-12-12 19:26:58 +02:00

Qortal GO 2.0 — Native Mobile Qortal Client

Qortal GO 2.0 is the native Android port of Qortal Hub (this repo is a fork of Qortal/Qortal-Hub, branch feature/qortal-go-mobilize). It wraps the Hub's React UI in a Capacitor/Android shell and adds a native Reticulum (RNS) stack on the phone, giving mobile users the Hub 2.0 P2P feature set: presence, voice calls, and direct file transfer over Reticulum — plus a mobile-first UI, OS notifications, biometric login, and battery-conscious background behavior.

For Hub developers: two small Hub-side changes are needed for full mobile ↔ Hub interop. Both are described in Required Hub changes below. Everything else is wire-compatible with the existing Hub.


Repository layout

Path What it is
src/ Shared React UI (same codebase as desktop Hub, with mobile components added)
src/bridge/capacitorBridge.ts The mobile equivalent of Electron's main-process bridges: installs window.presence / window.call / window.groupCall, presence overlay coordinator, Q-Chat file transfer sender logic
src/plugins/Reticulum.ts Capacitor plugin typings for the native Reticulum bridge
src/utils/qchatFileApi.ts Unified desktop/mobile Q-Chat file transfer API (desktop → electronAPI, mobile → Capacitor)
src/components/Mobile/ Mobile shell: top bar, tab bar, tabs overlay with folders, etc.
android/ Capacitor Android project
android/app/src/main/java/.../ReticulumPlugin.kt Native plugin: Kotlin RNS transport (reticulum-kt) + Chaquopy host for the Python bridge + file staging helpers
android/app/src/main/java/.../QortalForegroundService.java Foreground service: persistent status notification, alert/call notifications
android/app/src/main/python/presence_bridge.py The full Reticulum protocol bridge (presence, calls, group audio, file transfer) — shared with desktop, runs under Chaquopy on Android
electron/resources/presence_bridge.py The desktop copy of the same bridge — kept in sync; contains the file-transfer fixes the Hub needs to ship
electron/ Desktop Electron shell (unchanged upstream behavior, plus the bridge fix)

Building the Android app

Prerequisites: Node 20+, Android SDK (API 34), JDK 17.

npm install
npx vite build && npx cap copy android
cd android && ./gradlew assembleDebug
adb install -r app/build/outputs/apk/debug/app-debug.apk

The Python bridge (presence_bridge.py) and vendored RNS ship inside the APK via Chaquopy; no on-device Python setup is needed.

Mobile architecture in one paragraph

The WebView runs the same renderer as desktop Hub. Where desktop talks to Electron's main process (window.electronAPI, IPC to reticulum-daemon.ts), mobile installs equivalent bridges from capacitorBridge.ts that talk to ReticulumPlugin.kt, which hosts the identical presence_bridge.py in an embedded CPython (Chaquopy). Commands flow as JSON frames (presenceCommand with request/response correlation); events stream back over a single presence listener. The result: the phone speaks the exact same RNS wire protocol as the Hub — same presence envelopes, same call signaling, same QGAU audio framing, same QGCCTL1 link auth, same file transfer Resources.

Feature status (mobile ↔ Hub interop)

Feature Status
Presence (announce/heartbeat over RNS overlay) Works both ways
Q-Chat (blockchain chat) Works
Voice calls Hub → mobile Works — two-way audio over verified RNS link
Voice calls mobile → Hub ⚠️ Blocked by a Hub-side ordering precondition — see Hub change #1
Q-Chat file transfer mobile → Hub Works (mobile sender has the robustness fixes)
Q-Chat file transfer Hub → mobile, large files ⚠️ Reliable only after the Hub ships Hub change #2
OS notifications (PM summary + sender avatar), biometric login, mobile tabs/folders Mobile-only features

Required Hub changes

(See MOBILE_VOICE_HUB_DEV_NOTE.md in this repo for the full write-up.)

Symptom: Hub → mobile calls connect with clean two-way audio. Mobile → Hub calls stay on "connecting…" on the Hub and drop after a few seconds — even though the phone sends the exact same signed link auth the Hub accepts in the other direction.

Root cause (traced in electron/src/group-call.ts): it is not a protocol or signature problem — the GC_JOIN verifies. It's an ordering precondition:

  • The audio-link owner is chosen by address ordering (isLocalAddressReticulumAudioLinkOwner), not by who placed the call.
  • Inbound link audio is dropped unless the link is marked verified (audio-unverified-address).
  • applyVerifiedReticulumLinkAuthJoin only marks a link verified if the sender is already in room.participants at that instant.

When the phone initiates, its verified link-auth GC_JOIN arrives before the Hub has registered the caller as a participant, so the Hub closes the link (link-auth-no-participant), drops the audio, and never leaves "connecting". In the Hub-initiated direction the participant state is already in place, so the identical handshake succeeds.

The change: in applyVerifiedReticulumLinkAuthJoin, when the signed GC_JOIN verifies and the sender is a valid Qortal member of the call, admit the participant from the verified join instead of bailing with link-auth-no-participant. The verified join is itself authenticated proof of membership (Ed25519 over buildGcJoinSignedFields, already checked), so this removes the race without weakening security. No changes to wire format, audio framing, signing, key delivery, or the link-owner rule; Hub-initiated calls are unaffected.

2. File transfer: ship the updated presence_bridge.py

Symptom: large transfers (e.g. ~80 MB Hub → mobile) stall at exactly the first window of parallel chunks (8 × 1 MB) and the retry restarts from zero.

Root cause: the per-chunk QCHAT_FILE_CHUNK_ACK is a single link packet with no retry. Under congestion a lost ACK caused:

  1. the sender's 90 s ACK timeout to fail the whole transfer — and the un-ACKed chunk was never re-queued (next_chunk_index had already advanced → permanent hole in the file);
  2. dead links were never replaced, so the 8-link pool only shrank;
  3. re-accepting restarted the download from byte 0 (and could never fill the holes anyway).

The change (already implemented in this repo's electron/resources/presence_bridge.py, mirrored in the Android copy):

  • retry_chunks queue on the send root — an ACK timeout or mid-chunk link failure re-queues the chunk (consumed with priority by _start_qchat_file_resource_for_state) and emits retrying instead of failed; only the suspect link is torn down.
  • Receiver link reopenon_qchat_file_link_closed opens a replacement link while a receive is pending (the receiver is the link initiator; bounded by _QCHAT_FILE_RECEIVER_MAX_LINK_REOPENS = 64).
  • Resume — the receiver persists a <savePath>.part.meta ledger (transferId, size, sha256, completed chunks) per chunk; a re-accept preloads it and resumes instead of restarting. The meta is removed on completion, and both .part and meta are discarded on a final hash mismatch.

What to do: include this file in the next Hub release — nothing else changes. No new packet types; fully wire-compatible with old peers. The sender-side re-queue is the critical half, so Hub → mobile transfers of large files only become reliable once the Hub ships it.


Mobile-specific work in this fork (overview)

  • Q-Chat file transfer on mobile: native file staging (qchatFilePrepare copies content:// URIs to app cache + SHA-256), auto save paths, export of completed downloads to public Downloads (MediaStore), an Open button on received files, renderer-side Ed25519 verification of downloader link auth, pending-send persistence across app restarts.
  • Notifications: single in-place-updating direct-message summary notification ("You got N messages from X and Y") with the sender's avatar (fetched in the WebView and passed as a data URL — node MIME quirks and OEM skin differences handled), MIUI-aware status notification branding, in-app Qortino notifications with auto-dismiss countdown.
  • Performance/battery: deduped + rate-limited foreground service updates (was ~7 native calls/s, now ≤0.2/s), in-memory cache for hot secure-storage keys (73% bridge reads), visibility-aware polling (Reticulum status, balance, dashboard), WASM bcrypt wallet KDF (verified byte-identical to bcryptjs), lazy i18n locales (EN + active language only), avatar fetch outcome caching.
  • Mobile UX: tab bar + tabs overlay with folders, app library list layouts, biometric unlock flow (no keyboard pop on autofill), blocked-accounts flows for public/private nodes, Android back-button handling, DM voice call UI.

Upstream

Forked from Qortal/Qortal-Hub — see that repo for the desktop Hub documentation, i18n guidelines (docs/i18n_languages.md), and development docs (docs/development.md). License: GPL-3.0 (unchanged).

S
Description
Qortal GO mobile app 2.0, Reticulum calls, file transfers, optimized UI
Readme GPL-3.0 34 MiB
Languages
TypeScript 50.9%
JavaScript 20.9%
Python 16%
Fluent 9.7%
Java 1.1%
Other 1.3%