RebreakVpnService.onStartCommand crashed with SecurityException because Android 16's validateForegroundServiceType rejects the implicit 2-arg startForeground(). Now passes FOREGROUND_SERVICE_TYPE_SPECIAL_USE explicitly (Google's documented best practice) and guards the call so a failed foreground promotion stops the service cleanly instead of crashing the app. Verified vs reported Galaxy A54 / Android 16 signature (97% of crash events, 1-user crash loop). Bundles pending working-tree work across native/marketing/locales/mac + graphify-out rebuild. gitignore: google-services.json + /screenshots/. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
4.9 KiB
Voice-Calls — Debug-Zustand (2026-06-05)
Momentaufnahme beim Live-Debugging mit 2 Geräten (iOS + Android, beide über Metro). Ziel: Kinderkrankheiten der Call-Funktion per Teile-und-Herrsche fixen.
Architektur (Ist-Zustand)
Zwei Zustell-Pfade zum Callee — beide feuern IMMER parallel (Caller macht beides):
- Realtime
call-ring:<calleeId>→useIncomingCalls→receiveIncoming()+router.push('/call'). Greift nur wenn die App lebt + Realtime subscribed (FG, oder Android kurz nach BG). - Push
POST /api/calls/ring→sendCallRingPush:- iOS + voipToken → VoIP-PushKit →
AppDelegate.didReceiveIncomingPush→RNCallKeep.reportNewIncomingCall(CallKit) → JS-Forward viauseCallKeepEvents. - sonst (Android, iOS ohne voipToken) → Expo-Push (channel
calls,data.type='call'). Reagiert nur auf Tap (addNotificationResponseReceivedListener).
- iOS + voipToken → VoIP-PushKit →
Kern-Spannung: im FG entsteht Doppel-UI (in-app /call und CallKit/ConnectionService).
iOS-BG ist durch Apple auf CallKit festgenagelt (PushKit-Pflicht). Android-BG ist frei wählbar.
Verhaltens-Matrix
| # | Anruf | Callee-State | Erwartet | Beobachtet | Hypothese (Root Cause) |
|---|---|---|---|---|---|
| 1 | Android→iOS | FG | /call bleibt, klingelt |
/call erscheint, verschwindet nach ~Sek |
VoIP-Push feuert auch im FG → CallKit übernimmt → App→inactive → CallKit-endCall feuert mit appState≠active → onEnd-Guard (appState==='active') greift NICHT → declineCall() schließt /call. (Logs nötig) |
| 2 | iOS→Android | FG | korrekt | korrekt | — |
| 3 | Android→iOS | BG/locked | CallKit-Incoming (Apple) | nichts; nach Auflegen „verpasster Anruf"-Push; Telefon-App zeigt kein ReBreak-Audio | VoIP-Push erreicht Gerät nicht / CallKit nicht reported. Verdacht A: voipToken=null in DB → Fallback Expo-Push (im BG silent). Verdacht B: VoIP gesendet, aber APNs-Fehler (env/topic/cert). „Verpasster Anruf"-Push = Chat-DM aus logCallToChat. (Logs nötig) |
| 4 | iOS→Android | BG (App lebt) | ReBreak /call |
Android Telecom-native Incoming-UI | Realtime im BG → receiveIncoming → displayIncomingCall → ConnectionService = System-Telecom-UI. „Working as coded", aber Design-Mismatch: gewünscht ist Custom-/call (Full-Screen-Intent). |
Divide & Conquer — Phasen
- Phase 0 — Diagnose (non-destruktiv): 4 Szenarien durchspielen + Logs einsammeln
(
[call-ring] voip=?,[voip-push],[VoIP] didReceiveIncomingPush,[callkeep] end … appState=). Bestätigt #1 und #3. - Phase 1 — iOS FG (#1):
/callautoritativ machen; CallKit im FG unterdrücken / sauber programmatisch beenden mit „programmatic-end"-Flag;onEnd-Guard reparieren (inactive= FG). - Phase 2 — iOS BG (#3): VoIP-Pfad fixen — voipToken-Registrierung + APNs env/topic/cert verifizieren, bis CallKit im BG zuverlässig erscheint.
- Phase 3 — Android BG (#4): Entscheidung Custom-Full-Screen-Intent vs Telecom-UI; ggf. umbauen.
- Phase 4 — Aufräumen: Doppel-Push/Doppel-Log, Missed-Call-Handling, einheitlicher Teardown.
✅ BESTÄTIGTE Root Cause #3 (2026-06-05, via idevicesyslog + git)
Build 76 wurde aus dem alten with-voip-pushkit-ios.js kompiliert, wo die PushKit-Registry
eine lokale Variable war (let voipRegistry = PKPushRegistry(queue: nil)) → deallociert
sofort nach didFinishLaunching → eingehende VoIP-Pushes erreichen didReceiveIncomingPush nie
→ kein reportNewIncomingCall → kein CallKit-Wake. Token-Registrierung klappt trotzdem
(Sync-Callback), daher voip=1 am Server, aber Pushes verpuffen.
Beweise (idevicesyslog iPhone Air, Build 76):
[voip] register event fired, token: 33899c2b…✅ (Token-Reg ok)[voip-push] sent env=sandboxam Server ✅ (Push verlässt Server)[VoIP] didReceiveIncomingPush/reportNewIncomingCallFEHLEN komplett ❌- CallKit-Fehler
requesttransaction Code=4(unknown UUID) beim End-Versuch = Call war CallKit nie bekannt - „verpasster Anruf"-Push = Chat-DM aus
logCallToChatbeim späteren FG-Realtime-Replay, NICHT der Ring
Fix: bereits geschrieben (uncommitted) — Registry als self.voipRegistry retainen.
Liegt im ios/ReBreak/AppDelegate.swift (Jun 4 20:35), aber nie in einen Build kompiliert.
→ Dev-Rebuild (expo run:ios --device) kompiliert den Fix. Danach verifizieren dass
[VoIP] didReceiveIncomingPush feuert. Offen bleibt sekundärer Verdacht: Doppel-Registry
durch JS registerVoipToken() (Library erzeugt 2. unretained Registry) — empirisch nach Rebuild prüfen.
Apple-Constraint (kein Bug)
Auf iOS Background/locked ist die Incoming-UI zwingend CallKit (PushKit verlangt
reportNewIncomingCall in derselben Run-Loop, sonst killt iOS die App). Der Custom-/call-Screen
kann dort erst NACH „Annehmen" erscheinen — nicht als Klingel-UI.