Pivot away from the native-LiveKit rewrite. Discovery: the 'voip' UIBackgroundMode
already keeps the WebView's call audio alive when backgrounded (confirmed on device),
so background audio is solved WITHOUT native media. The only issue was CallKit
reserving the mic. So use CallKit purely for the incoming RING:
- Plugin CXAnswerCallAction: fulfill, then immediately end the CallKit call
(reportCall endedAt) to RELEASE the mic, and fire answerCall to the WebView after a
~1s beat so iOS tears down the CallKit audio session first. didActivate no longer
reconfigures the session (was fighting WebKit).
- home.html: outgoing calls no longer register with CallKit (WebView-only → mic works);
incoming still rings via CallKit → hands off to the WebView on answer.
Net: native full-screen ring + working mic + background audio + all existing call
features. Needs a Codemagic build; then flip CALLKIT_ENABLED=1 to test.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
MIC BROKEN with CallKit: a CallKit call reserves the microphone, so the WebView's
WebRTC can't capture it — calls are unusable until native LiveKit media lands. Add a
server kill-switch (CALLKIT_ENABLED, default OFF) so CallKit can be flipped without an
app rebuild: /api/meetings/config now returns callkit; setupNativeCall bails when off
(-> WebView calls, mic works); push.js only sends VoIP/CallKit pushes when enabled.
Deploying with the flag unset immediately restores working WebView calls.
RE-RING: a late cancel push for an already-declined call hit the plugin's 'unknown
uuid' path and re-reported a fresh incoming call (~1s re-ring). Track endedCalls and
make a late cancel for an already-ended call a no-op.
Server part deploys now (no rebuild); plugin re-ring fix ships with the native build.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Bug: a killed/backgrounded callee is woken only for the CallKit ring and has no
WebSocket yet, so the existing dm-call active:false (WS-only) never reaches it and
it keeps ringing after the caller hangs up.
Fix: send a 'cancel' VoIP push on call teardown.
- push.js: sendCallCancel() sends a {type:'cancel',callUUID} VoIP push to the user's
ios-voip tokens; invites now carry type:'invite'.
- calls.js: endDmCallByRoom + endGroupCallByRoom fire sendCallCancel to the rung users.
- NativeCallPlugin: on a cancel push, end the reported call (reportCall endedAt); if the
invite was never seen, report-then-end to satisfy iOS's 'report a call per VoIP push'.
Server part deploys now; the plugin part needs the next Codemagic build.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- didReceiveIncomingPush: normalise payload.dictionaryPayload ([AnyHashable:Any])
to [String:Any] before storing/using (the reported build error at :121).
- CXProviderConfiguration(localizedName:) instead of the no-arg init (available on
all deployment targets, avoids an availability edge).
- Make two 'calls[uuid] ?? [:]' bindings explicitly [String:Any] to avoid empty-
literal inference ambiguity.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The native call feature (iOS). Backward-compatible: without the plugin (current
builds) nativeCallOn() is false and every CallKit branch is skipped, so web/older
builds behave exactly as before.
Native (mobile/plugins/native-call, a local Capacitor plugin like audio-route):
- PushKit: registers for VoIP pushes, reports the VoIP token to JS (-> /api/v1/devices
'ios-voip'). On an incoming VoIP push, reports a CallKit incoming call (full-screen
ring, works when the app is force-killed).
- CallKit: answer/decline/end -> events to JS; configures the call AVAudioSession on
didActivate so the WebView's WebRTC audio rides a call-priority session (background).
- Outgoing calls register with CallKit too (reportOutgoingCall) so they get the same
active-call background-audio context.
- NativeCall.podspec (frameworks CallKit/PushKit/AVFoundation); added to mobile deps;
ios-patch.sh now sets UIBackgroundModes = [audio, voip] (voip required for PushKit).
Web bridge (home.html): setupNativeCall() registers the VoIP token, joins on CallKit
answer, leaves/declines on CallKit end; on CallKit devices the in-app call-invite popup
+ WebAudio ring are suppressed (the system rings instead); outgoing calls are reported
to CallKit; call-end events dismiss the CallKit call. calls.js threads a stable call
uuid through the dm-call/group-call WS events + start responses so both sides can match
the CallKit call.
Needs a Codemagic build to compile the plugin; first on-device iteration expected.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>