Mobile App — Open Decisions (memos for Hari)¶
Two decisions block shipping; both are prepared here with a recommendation so they only need a yes/no.
1. Linphone licence: GPLv3 vs commercial (task #13)¶
The constraint. linphone-iphone (our astradial-ios base) is GPLv3. The SDK is dual-licensed. Distributing the app (App Store counts) obliges us to either comply with GPLv3 or buy a commercial licence from Belledonne Communications.
| Option | Cost | Implications |
|---|---|---|
| A. GPLv3 compliance | ₹0 | Publish our app source (a public astradial-ios repo with our UI included). Competitors can read it; they still can't use our backend, branding, or trademarks. App Store + GPLv3 has friction (Apple's terms historically conflict with GPL; VLC solved it with explicit additional permissions — Belledonne would need to grant none since we are the distributor of our fork, but our own code must be GPL-licensed too). |
| B. Commercial licence | Quote from Belledonne (typically per-product, four–five figures €/yr) | Closed source, no obligations. Contact: linphone.org/contact. |
| C. Rewrite UI against only the SDK under commercial SDK licence | Engineering time | We already replaced most UI, but we still link app-layer GPL code (CallViewModel, TelecomManager, CoreContext…). Full clean-room of those is weeks. |
Recommendation: start with A (publish the fork) for TestFlight and early hospital pilots — it costs nothing and unblocks shipping — and request a commercial quote from Belledonne in parallel. Switch to B before broad App Store marketing if the quote is acceptable. The real moat is the PBX platform, not the phone UI.
DECIDED (Hari, 2026-06-11): Option A. The fork will be published as an open-source GitHub repo. Compliance timing rule: the repo must be public (with GPL licence file + a source link in the app's About screen) no later than the first build that reaches anyone outside the company — external TestFlight or App Store. Internal team testing before that is fine with the repo still private. Commercial quote from Belledonne remains optional later.
2. VoIP push for inbound calls when the app is killed (task #12)¶
The problem. iOS kills background SIP registrations; without a server push, a killed app cannot receive calls. The app already contains working PushKit/CallKit plumbing (Linphone's EarlyPushkitDelegate + TelecomManager) — what's missing is a server that sends the VoIP push when an INVITE arrives.
| Option | Effort | Notes |
|---|---|---|
| A. Asterisk pre-dial push hook | ~2–4 days server | Dialplan pre-dial handler (or ARI) on the user's endpoint: before Dial, POST to a small push service that sends an APNs VoIP push (token stored per user at app login), wait ~2s for the app to wake and REGISTER, then Dial as normal. Fits our existing AstraPBX API + per-user asterisk_endpoint model. |
| B. Flexisip proxy in front of Asterisk | Heavy infra | Belledonne's own solution; a second SIP tier to deploy/operate. Overkill for our stack. |
| C. v1 ships without killed-app inbound | 0 | Calls reach desk phones/queues anyway (hospital reality: the app is the MD's monitoring device + secondary line). Missed calls already become tickets + (future) FCM notification. |
Recommendation: ship v1 with C, build A immediately after — the auto-ticket pipeline means no missed call is silently lost in the meantime, and option A slots into the existing API (org_users gains apns_voip_token, app posts it after login, a pushGateway job fires on inbound INVITE).
DECIDED (2026-06-29): Option A. Approved, and the iOS side (Part B) is built + shipped (dormant until the gateway exists). Key clarifications:
- iOS must use PushKit VoIP push + CallKit, NOT FCM. On iOS, FCM only relays through APNs, and ordinary notifications can't relaunch a force-quit app or show the ring screen — Apple requires PushKit for this. PushKit is also the battery-saving path: the app stays fully suspended between calls (no persistent SIP registration / keepalive) and is woken over Apple's shared push channel. ("FCM saves battery" is an Android fact; the Android app uses FCM high-priority — see §E.)
- APNs Auth Key, not VoIP cert: use one
.p8Auth Key (covers VoIP + alerts, dev + prod). It's a server secret — lives in the platform, never the app repo. - iOS DONE (Part B, commit
ff4b4de8b):aps-environmentrestored;PushKitManager(inEarlyPushkitDelegate.swift) requests the VoIP token, forwards it to liblinphone, and on a push reports to CallKit immediately + callsprocessPushNotification;displayIncomingCallmade idempotent on callId.LinphoneAppnow always requests a token (a signed-in user never did before — the real gap). - Part A APNs key DONE (2026-06-29): Key ID
PF5LTXJ66D, Team IDL7RZB3SY47, VoIP topiccom.astradial.softphone.voip(env: Sandbox & Production, Team-scoped). The.p8is in Hari's local~/AstradialDevelopment/Secrets/(outside every repo) — in Part C load it into the platform secret store and add*.p8to the platform.gitignore. Remaining in Part A: the Xcode Push Notifications capability. - Part C BUILT (2026-06-29, PR #483 →
staging): inastradial-platformbranchfeat/ios-voip-push: migration + User model (voip_push_tokenetc., all nullable),POST /api/v1/users/voip-push-token(token registration),apnsService.js(APNs VoIP via the.p8; stdlib http2 + jsonwebtoken, no new dep),GET /api/v1/asterisk/voip-push-wait(x-internal-key; push then poll pjsip registrations), and a dialplan pre-dial hook GATED onvoip_push_token && INTERNAL_API_URL. Prod-safe by design: deploys only viastagingbranch → staging VPS (94.136.188.221); prod (147.93.168.216) deploys manually frommain. And the gate means prod dialplans are byte-for-byte unchanged (prod has no tokens and noINTERNAL_API_URL). - Deploy = STAGING only. Merge PR #483 → run migration (
npx sequelize-cli db:migrateinapi/, with/before the code since new code reads the new columns) → set staging env (APNS_KEY_ID=PF5LTXJ66D,APNS_TEAM_ID=L7RZB3SY47,APNS_BUNDLE_ID=com.astradial.softphone,APNS_KEY_PATH,INTERNAL_API_URL,VOIP_PUSH_TIMEOUT_MS=8000) + scp the.p8+ restart API → verifyres_curlloaded → regenerate the AstraPrivate dialplan. The app↔stagepbxtoken POST is blocked by Cloudflare Access, so seed the token in the DB or add a CF Access bypass for that path. VPS steps need SSH (Claude is blocked → Hari runs them). - Known iOS gap (needs device test): pre-dial model fires the push before the INVITE exists, so the CallKit placeholder's call-id can't match the real INVITE → possible double-ring; reconcile (reuse the placeholder UUID for the next incoming INVITE) once tested on a device.
- iOS launch-hang gotcha: the PushKit registry MUST run on the main queue, NOT
coreQueue— token delivery oncoreQueuereenteredCoreContextstartup and hung the app at launch.PushKitManagerowns a.main-queue registry;start()is idempotent. (Fixed 2026-06-29, commitfc80b582e.)
Status of the rest of the rebrand (task #3) — DONE (2026-06-29)¶
Display name Astradial, app icon, native splash, paid team (Astradial Private Limited L7RZB3SY47), bundle id com.astradial.softphone (the old com.astradial.phone + its app groups were globally claimed by the prior free personal team — had to use fresh ids), app group group.com.astradial.phone.shared, project/target/scheme renamed LinphoneApp→Astradial, Push Notifications capability, committed Firebase plist replaced with a dummy (real misssellerai one is local/skip-worktree). Remaining: TestFlight app record (task #14).
Android-specific decisions (June 2026)¶
A. Native target = Samsung One UI (DECIDED, Hari)¶
The Android app matches the stock Samsung Galaxy Phone/Contacts app (One UI), not Pixel/Material You. The Material-3 Figma kits are component sources, re-themed to One UI. On Galaxy hardware the system font is SamsungOne, so system text styles give One UI typography for free. See Android UI Spec.
B. Clean split — OSS dialer vs. private overlay (DECIDED, Hari)¶
Public astradial-android-oss = native dialer on Linphone, no Firebase/auth/ analytics. Private astradial-android overlays Firebase sign-in + MD Analytics + Tickets. Mechanism is just google-services.json presence (Linphone already gates Firebase on it). See repo-cicd and astradial-android.
C. SIP transport = UDP only (DECIDED, Hari)¶
Calls register/stream over UDP to devsip.astradial.com:5060 (prod) / stagesip.astradial.com:5080 (staging) — matches the iOS app. Accepted risk: UDP SIP has no transport encryption and media is not SRTP-forced, so signaling + RTP are cleartext on the wire; confidentiality relies on Astradial's own network path (Asterisk + WireGuard), not on-wire crypto. Revisit TLS/SRTP only if calls traverse untrusted networks. The platform HTTPS API is unaffected.
D. GPLv3 under the clean split¶
linphone-android is GPL-3.0. The public OSS fork is compliant by being public. But the private overlay still links GPL code, so a distributed Play Store build carries GPL obligations on the whole app — before any external release, either publish the overlay source (GPL) or buy a Belledonne commercial licence. Same Option-A timing rule as iOS (§1 above): private is fine for internal testing; must be open before the first external build. The moat is the PBX platform, not the app source.
E. FCM inbound-call push (Android analog of iOS VoIP push, task #12)¶
Android's killed-app inbound is easier than iOS: a high-priority data FCM wakes CorePushReceiver → liblinphone registers → USE_FULL_SCREEN_INTENT / Telecom rings. Reuses the same server-side Asterisk pre-dial → push gateway as iOS (decision §2 above), emitting FCM instead of APNs. v1 can also keep a foreground-service registration as a fallback. Needs the Astradial Firebase project (private overlay) + a per-user FCM token endpoint.
F. Disregard astradial-softphone-android¶
An earlier May-2026 linphone-android fork (astradial-softphone-android, private) carried a text/theme rebrand + QR-login + signing wiring. Per Hari (June 2026) it is not the seed — the new repos are taken directly from current Belledonne upstream. The old repo is left untouched (not archived, not merged); its git history was secret-scanned and is clean.