# 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): 1. **Realtime** `call-ring:` → `useIncomingCalls` → `receiveIncoming()` + `router.push('/call')`. Greift nur wenn die App lebt + Realtime subscribed (FG, oder Android kurz nach BG). 2. **Push** `POST /api/calls/ring` → `sendCallRingPush`: - **iOS + voipToken** → VoIP-PushKit → `AppDelegate.didReceiveIncomingPush` → `RNCallKeep.reportNewIncomingCall` (CallKit) → JS-Forward via `useCallKeepEvents`. - **sonst** (Android, iOS ohne voipToken) → Expo-Push (channel `calls`, `data.type='call'`). Reagiert **nur auf Tap** (`addNotificationResponseReceivedListener`). 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)**: `/call` autoritativ 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=sandbox` am Server ✅ (Push verlässt Server) - **`[VoIP] didReceiveIncomingPush` / `reportNewIncomingCall` FEHLEN komplett** ❌ - CallKit-Fehler `requesttransaction Code=4` (unknown UUID) beim End-Versuch = Call war CallKit nie bekannt - „verpasster Anruf"-Push = Chat-DM aus `logCallToChat` beim 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.