Mobile App Access (WSS softphone line)¶
Status: ACTIVE — platform side on staging (PR #515 + #516 merged; #516 decouples Mobile App Access from Call Routing). Staging E2E verified 2026-07-10: toggling sip_port via PUT /users/:id flips the endpoint transport-udp:5080 ⇄ transport-wss:8089 (+alaw+DTLS) after the ~20s single-org auto-reload; desk lines untouched. Android login gate DONE (commit 6bc29d83 — picker/manual admit only wss lines). iOS login gate DONE too (astracall, 2026-07-12): quick-connect gates on sip_port='wss' + non-empty asterisk_endpoint only — never on routing_type/ring_target. Fixed a bug where the old ring_target != 'phone' clause hid wss lines whose inbound was routed to a SIM/phone. Pending: prod promotion. The "when a line receives inbound calls on the app" decision (call routing) is still deferred — separate from the login gate.
What it is¶
A per-user "Mobile App Access" control: whether an org user's SIP line can register from the AstraCall mobile app (iOS + Android) over WebRTC/WSS. It replaces the old manual, unmanaged workflow (UPDATE users SET sip_port='wss' + regen by hand) with a managed, admin-controlled toggle surfaced in the editor and reflected in the app.
The model: two ORTHOGONAL axes¶
Mobile App Access and Call Routing are independent — the first cut wrongly coupled them (the Mobile toggle was hidden for SIM/AI-routed lines, and "Mobile App" was jammed into the routing dropdown). Corrected 2026-07-09 (revised again):
- Mobile App Access =
sip_port='wss'(a WebRTC endpoint). Controls whether the line can log in from the AstraCall app. On/off, shown for every line regardless of routing. - Call Routing (
routing_type/ring_target) = where inbound calls ring. Unchanged 3 options: SIP / IP Phone (sip/ext), AI Bot (ai_agent), Phone Call = SIM (sip/phone, rings cellular over PSTN). No "Mobile App" routing option — that conflated login with routing.
Any combination is valid — e.g. Mobile App Access ON + routing = Phone (SIM): the user logs into the app (outbound click-to-call / tickets / presence) while inbound rings their SIM.
sip_port | Endpoint flavour | App can log in? |
|---|---|---|
5080 (default) / 5060 (legacy) | UDP desk/ATA | no |
wss | transport-wss + webrtc | yes |
Deferred: whether a line should actually receive inbound calls on the app (vs SIM/desk) is a separate call-routing decision, to be designed later (on mobile or in the editor). Mobile App Access is only about login — never a receive-call gate.
Data model — no new column¶
users.sip_port='wss' is the single source of truth, already validated end-to-end (api/src/services/asterisk/sipEndpointOptions.js, SIP_PORTS=['5080','5060','wss']) and already wired to config regen. Everywhere else it's exposed as a derived boolean mobile_enabled = (sip_port === 'wss'). No migration: lines flipped manually before this (e.g. Om Chamber ext 1001) already read wss and light up automatically.
- The generator (
userProvisioningService.js) emitstransport=transport-wss,webrtc=yes,allow=alawfor wss lines; UDP desk/ATA endpoints stay byte-identical. See concurrent-call-cap.md — each wss line is a transcode/CPU cost, which is why access is admin-granted. - Toggling off sets
sip_port='5080'(a former5060legacy line reverts to5080— accepted; 5060 is a desk-hardware SIP-ALG workaround that wouldn't be a mobile line).
API — reuse PUT /api/v1/users/:id¶
- Enable:
PUT /api/v1/users/:id {"sip_port":"wss"}→ 200 (updated user). - Disable:
{"sip_port":"5080"}. - Regen: already automatic — a
sip_portchange fires a single-org, background Asterisk reload (not restart; live calls safe) via the existing mutex'd deploy path. No new route, nocheck:routesupdate. - RBAC (admin-only): the route is member-accessible for self-service fields, so the
sip_portguard is field-level — enforced only whensip_portactually changes. A non-admin JWT gets 403; master/internal keys bypass (same asrequireRole). Pinned byapi/tests/mobile-access-admin-only.test.js. Rationale: each wss line is a capacity cost, so it's granted, not self-claimed.
Editor UI (editor/app/dashboard/[orgId]/users/page.tsx)¶
- Edit dialog → "Mobile App Access" switch (next to Health Check) — independent of Call Routing. On →
sip_port='wss'; off →5080. The SIP Port select (5080/5060) shows only while Mobile App Access is off (WSS has no port choice) — this also fixes the blank-select bug on already-wss lines. - Edit dialog → Call Routing is the original 3 options (SIP / AI Bot / Phone-SIM), untouched — it no longer changes
sip_port. - Users list → "Mobile" column next to "Mobile Push": an inline on/off
Switchon every row (including SIM/Phone- and AI-routed lines) for at-a-glance "who can log in on the app" + one-click grant. - Type widened:
sip_port?: "5080" | "5060" | "wss"(editor/lib/pbx/client.ts). - Add-user form intentionally unchanged — create a user, then flip Mobile on.
Mobile apps — gate login on the Mobile App Access toggle¶
Decision (2026-07-10, final): the mobile app logs in only lines whose "Mobile App Access" toggle is ON (sip_port='wss'). Everything else is not provisioned for the app.
Reconcile the two rules that sound opposite but aren't:
- Do NOT gate on call-routing type. A
wssline whose inbound rings the SIM (ring_target='phone', e.g. staging ext 1003) still logs in. Routing ≠ access. - DO gate on the toggle (
sip_port='wss'). Non-wssSIP lines are excluded from the picker and rejected on manual entry.
Behaviour (both platforms): - Line picker: shows only sip_port='wss' lines. Non-mobile lines are not listed. - Manual entry / QR: a typed/scanned user that isn't a wss line is rejected with "This SIP user is not provisioned" — checked against the org's mobile-enabled set before registering. (The 20s register-timeout remains as a backstop.) - Both read sip_port from GET /api/v1/users (already returned).
Android (Mobile App/astradial-android, branch private-main) — DONE (commit 6bc29d83)¶
AstradialApi.getOrgSipUsers()gates onsip_port=='wss'(wasrouting_type=sip && ring_target!=phone); exposesendpoint(PJSIP username) onOrgSipUser. Picker, in-app "change line", and cold-start re-provision all consume it → gated for free.OneUiSettingsFragment.showManualDialogrejects a user not in the mobile-enabled set withastradial_sip_not_provisionedbefore provisioning.- No QR-scan provisioning exists in the Astradial overlay — nothing to gate there.
iOS — DONE (2026-07-12, the App Store app astradial-astracall)¶
The store app is Mobile App/astradial-astracall (com.astradial.astracall), NOT the old Linphone fork astradial-ios. Implemented there:
-
OrgUserstruct already carriessipPort(sip_port) +asteriskEndpoint—AstraCall/Screens/NativePhoneUI.swift(computedmobileEnabled = sipPort == "wss"). -
Quick-connect filter —
Gates ONLY on the Mobile App Access toggle (AstraCall/Screens/OnboardingUI.swift,OnboardingSIPQuickView.sipUsers:sip_port='wss') + a real endpoint — never onrouting_type/ring_target. This fixed a bug where the oldring_target != 'phone'clause hidwsslines whose inbound was routed to a SIM/phone (they must still register). AI agents (sip_port='5080') stay hidden unless an admin grants Mobile access. -
Manual entry / QR — Manual and Scan QR are separate Connect-Type tabs on the "Connect Your Line" screen; provisioning re-guards
mobileEnabledbefore registering.
Interactions¶
- SIM Calls Only (
ring_target=phone) controls where inbound calls ring; it is orthogonal to Mobile App Access. Awssline whose inbound rings the SIM can STILL register from the app (it just receives calls on the SIM). The pickers gate onsip_port='wss'only, never onring_target— do not re-add a routing exclusion. - Codec: wss lines are
allow=alaw(already settled — no FEC, so cellular is choppy; the SIM path is the cellular answer). See [AstraCall codec constraints]. - Capacity: cost is per concurrent webrtc call (transcode), not per registered line; no hard cap in v1 — the editor "Mobile" column/count is the operator's self-police signal.
- VoIP push tokens: a token is registered only after a successful WSS registration, so a non-
wssline naturally never registers a token (it can't register at all) — no login gate and no server-side push-token change needed.
VoIP push registration on app open (2026-07-11)¶
The app ensures its device push token is registered on every app open (not just when the token first arrives), so a mobile line reliably wakes for inbound calls.
Flow (strict, permission-gated): on app foreground (iOS scenePhase == .active, and when the SIP line reaches .registered; Android onResume): 1. SIP line registered + endpoint known? If no → no-op. 2. Push token delivered yet? If no → skip (the token-arrival callback re-enters later). 3. GET /api/v1/users/voip-push-status?asterisk_endpoint=…&token=… → if registered && token_matches → done (no redundant POST). 4. Otherwise, notification-permission gate: authorized → register (POST /users/voip-push-token); not-yet-asked (on an app-open) → prompt, register on grant; denied → do NOT register. Registration is intentionally gated on notification permission even though VoIP push technically doesn't need it.
Endpoints (org-scoped via authenticateOrg, keyed by asterisk_endpoint to match the POST): - GET /api/v1/users/voip-push-status → {registered, token_matches, environment, platform, updated_at}. Never returns the raw token. token_matches:false on a rotated token (reinstall/new device) → app re-registers. 404 if no user for that endpoint in the org. App treats non-200/network error as "not registered" and falls back to registering (graceful — an un-deployed server never breaks the app). - DELETE /api/v1/users/voip-push-token?asterisk_endpoint=… → self-deregister for the app's Settings → Revoke Notification Access (clears the token; the wake CURL drops from the dialplan on the next regen). Distinct from the editor's admin DELETE /users/:id/voip-push-token. iOS can't revoke its own OS notification permission, so Revoke = deregister server-side and deep-link to iOS Settings.
Bundle id is fixed per platform (not free-text): iOS com.astradial.astracall, Android com.astradial.phone. The editor's Mobile Push dialog derives it from the platform (field disabled).
Implementations: iOS PushKitManager.ensureRegisteredOnOpen() (Mobile App/astradial-astracall). Android parity is speced in VOIP-PUSH-ON-OPEN-HANDOFF.md in the android repo (FCM + POST_NOTIFICATIONS; Pulse analytics is a no-op stub there).
Rollout¶
- API + Editor (this branch) → PR to staging → verify on stageeditor: toggle a staging user, confirm
pjsip show endpointshowstransport-wss/webrtc: trueafter the auto reload, toggle off, confirm revert; confirm zero diff on untouched desk lines. - Hari promotes staging → main (prod). Existing manually-flipped prod lines keep working. (PR #515 merged to
staging2026-07-09.) - Mobile apps: no login/picker change — login stays unrestricted by routing type (see above). The "when does a line receive calls on the app" decision is deferred; revisit here when designed.
- Docs finalised here once staging E2E confirms.