feat(voip): iOS CallKit Recents integration#7152
feat(voip): iOS CallKit Recents integration#7152diegolmello wants to merge 1 commit intofeat.voip-lib-newfrom
Conversation
WalkthroughThese changes conditionally disable CallKeep functionality in China regions. A new region detection utility checks for cellular providers with country code "460" or falls back to system locale comparison. CallKeep setup and incoming call reporting are now gated to skip execution when the region is detected as China. Changes
Estimated code review effort🎯 2 (Simple) | ⏱️ ~12 minutes 🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@ios/Libraries/VoipRegion.swift`:
- Around line 8-17: The isChina() check is recomputed on every call causing
inconsistent gating; change it to compute once per app launch and reuse that
value. Add a static cached boolean (e.g., a private static var or a static let
initializer) inside the same VoipRegion/VoipRegion.swift scope and have
isChina() return the cached value; compute the cached value by running the
existing logic (CTTelephonyNetworkInfo/serviceSubscriberCellularProviders loop
and fallback to NSLocale.current.regionCode) exactly once at initialization.
Ensure the cache is initialized in a thread-safe way (use Swift's static let or
an explicitly synchronized static var) and replace all uses of isChina() to rely
on this cached decision.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 56e05e83-ff5b-4e4f-9038-c8ba6a4a03f7
📒 Files selected for processing (4)
ios/AppDelegate.swiftios/Libraries/AppDelegate+Voip.swiftios/Libraries/VoipRegion.swiftios/RocketChatRN.xcodeproj/project.pbxproj
📜 Review details
🧰 Additional context used
🧠 Learnings (3)
📓 Common learnings
Learnt from: CR
Repo: RocketChat/Rocket.Chat.ReactNative PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-07T17:49:17.538Z
Learning: Applies to app/lib/services/voip/**/*.{ts,tsx} : Implement VoIP with WebRTC peer-to-peer audio calls in app/lib/services/voip/ using Zustand stores instead of Redux, with native CallKit (iOS) and Telecom (Android) integration; keep VoIP and VideoConf separate
📚 Learning: 2026-04-07T17:49:17.538Z
Learnt from: CR
Repo: RocketChat/Rocket.Chat.ReactNative PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-07T17:49:17.538Z
Learning: Applies to app/lib/services/voip/**/*.{ts,tsx} : Implement VoIP with WebRTC peer-to-peer audio calls in app/lib/services/voip/ using Zustand stores instead of Redux, with native CallKit (iOS) and Telecom (Android) integration; keep VoIP and VideoConf separate
Applied to files:
ios/AppDelegate.swiftios/Libraries/AppDelegate+Voip.swiftios/RocketChatRN.xcodeproj/project.pbxproj
📚 Learning: 2026-03-05T06:06:19.755Z
Learnt from: divyanshu-patil
Repo: RocketChat/Rocket.Chat.ReactNative PR: 6957
File: ios/RCTWatchModule.mm:19-24
Timestamp: 2026-03-05T06:06:19.755Z
Learning: In the Rocket.Chat React Native iOS app, `WCSession` (WatchConnectivity) is activated with its delegate in `ios/RocketChat Watch App/Loaders/WatchSession.swift`, not in `RCTWatchModule.mm`. Since `WCSession.defaultSession` is a singleton, activating it once in `WatchSession.swift` is sufficient; `RCTWatchModule.mm` does not need to re-activate or re-set the delegate.
Applied to files:
ios/AppDelegate.swift
🔇 Additional comments (3)
ios/RocketChatRN.xcodeproj/project.pbxproj (1)
308-309: Project wiring looks correct for the new Swift helper.
VoipRegion.swiftis properly referenced and compiled in both app targets, and added to theLibrariesgroup.Also applies to: 648-648, 1157-1157, 2131-2131, 2412-2412
ios/AppDelegate.swift (1)
30-38: Good CallKeep setup gating and Recents enablement.This correctly enables iOS Recents for non-CN and skips CallKeep setup for CN devices.
ios/Libraries/AppDelegate+Voip.swift (1)
43-60: MIIT gate is applied in the right place for incoming VoIP pushes.Skipping
prepareIncomingCallandreportNewIncomingCallwhile still reachingcompletion()matches the intended CN behavior.
2143aba to
2a098f7
Compare
36df4ae to
337827f
Compare
337827f to
a5e7355
Compare
Configure CallKit natively in AppDelegate with includesCallsInRecents: true so Phone.app Recents shows entries for incoming Rocket.Chat calls. Gate all CallKit surface (setup + prepareIncomingCall + reportNewIncomingCall) behind a VoipRegion helper that iterates cellular subscribers for MCC=460 and falls back to locale regionCode for no-SIM devices, satisfying MIIT compliance on any CN SIM.
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@app/lib/services/voip/MediaSessionStore.ts`:
- Line 71: The code sets an invalid property on the MediaSignalingSessionConfig
object: remove the unsupported requestInitialStateSignals: false entry from the
config in MediaSessionStore (the object passed when constructing/configuring the
media signaling session). Locate the config usage in MediaSessionStore.ts
(search for MediaSignalingSessionConfig or the config literal used to create the
media signaling session) and delete the requestInitialStateSignals property so
the config matches the MediaSignalingSessionConfig type.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 986cb885-1fb7-49d2-a290-c2ad38aa16a1
📒 Files selected for processing (7)
app/lib/services/voip/MediaSessionInstance.tsapp/lib/services/voip/MediaSessionStore.tsios/AppDelegate.swiftios/Libraries/AppDelegate+Voip.swiftios/Libraries/VoipRegion.swiftios/RocketChatRN.xcodeproj/project.pbxprojpackages/rocket.chat-media-signaling-0.2.0-rc.0.tgz
✅ Files skipped from review due to trivial changes (3)
- app/lib/services/voip/MediaSessionInstance.ts
- ios/RocketChatRN.xcodeproj/project.pbxproj
- ios/Libraries/AppDelegate+Voip.swift
🚧 Files skipped from review as they are similar to previous changes (2)
- ios/AppDelegate.swift
- ios/Libraries/VoipRegion.swift
📜 Review details
🧰 Additional context used
📓 Path-based instructions (5)
**/*.{js,jsx,ts,tsx,json}
📄 CodeRabbit inference engine (CLAUDE.md)
Configure Prettier with tabs, single quotes, 130 character width, no trailing commas, arrow parens avoid, and bracket same line
Files:
app/lib/services/voip/MediaSessionStore.ts
**/*.{js,jsx,ts,tsx}
📄 CodeRabbit inference engine (CLAUDE.md)
Use ESLint with
@rocket.chat/eslint-configbase configuration including React, React Native, TypeScript, and Jest plugins
Files:
app/lib/services/voip/MediaSessionStore.ts
**/*.{ts,tsx}
📄 CodeRabbit inference engine (CLAUDE.md)
Use TypeScript with strict mode enabled and configure baseUrl to app/ for import resolution
**/*.{ts,tsx}: Use TypeScript for type safety; add explicit type annotations to function parameters and return types
Prefer interfaces over type aliases for defining object shapes in TypeScript
Use enums for sets of related constants rather than magic strings or numbers
Files:
app/lib/services/voip/MediaSessionStore.ts
app/lib/services/voip/**/*.{ts,tsx}
📄 CodeRabbit inference engine (CLAUDE.md)
Implement VoIP with WebRTC peer-to-peer audio calls in app/lib/services/voip/ using Zustand stores instead of Redux, with native CallKit (iOS) and Telecom (Android) integration; keep VoIP and VideoConf separate
Files:
app/lib/services/voip/MediaSessionStore.ts
**/*.{js,ts,jsx,tsx}
📄 CodeRabbit inference engine (AGENTS.md)
**/*.{js,ts,jsx,tsx}: Use descriptive names for functions, variables, and classes that clearly convey their purpose
Write comments that explain the 'why' behind code decisions, not the 'what'
Keep functions small and focused on a single responsibility
Use const by default, let when reassignment is needed, and avoid var
Prefer async/await over .then() chains for handling asynchronous operations
Use explicit error handling with try/catch blocks for async operations
Avoid deeply nested code; refactor complex logic into helper functions
Files:
app/lib/services/voip/MediaSessionStore.ts
🧠 Learnings (3)
📓 Common learnings
Learnt from: CR
Repo: RocketChat/Rocket.Chat.ReactNative PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-07T17:49:17.538Z
Learning: Applies to app/lib/services/voip/**/*.{ts,tsx} : Implement VoIP with WebRTC peer-to-peer audio calls in app/lib/services/voip/ using Zustand stores instead of Redux, with native CallKit (iOS) and Telecom (Android) integration; keep VoIP and VideoConf separate
📚 Learning: 2026-04-07T17:49:17.538Z
Learnt from: CR
Repo: RocketChat/Rocket.Chat.ReactNative PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-07T17:49:17.538Z
Learning: Applies to app/lib/services/voip/**/*.{ts,tsx} : Implement VoIP with WebRTC peer-to-peer audio calls in app/lib/services/voip/ using Zustand stores instead of Redux, with native CallKit (iOS) and Telecom (Android) integration; keep VoIP and VideoConf separate
Applied to files:
app/lib/services/voip/MediaSessionStore.ts
📚 Learning: 2026-04-07T17:49:17.538Z
Learnt from: CR
Repo: RocketChat/Rocket.Chat.ReactNative PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-07T17:49:17.538Z
Learning: Applies to @(app/sagas/videoConf.ts|app/lib/methods/videoConf.ts) : Manage video conferencing via Redux actions/reducers/sagas in app/sagas/videoConf.ts and app/lib/methods/videoConf.ts using server-managed Jitsi integration; do not conflate with VoIP
Applied to files:
app/lib/services/voip/MediaSessionStore.ts
🪛 GitHub Check: ESLint and Test / run-eslint-and-test
app/lib/services/voip/MediaSessionStore.ts
[failure] 71-71:
Object literal may only specify known properties, and 'requestInitialStateSignals' does not exist in type 'MediaSignalingSessionConfig'.
a5e7355 to
9736652
Compare
There was a problem hiding this comment.
🧹 Nitpick comments (1)
ios/RocketChatRN.xcodeproj/project.pbxproj (1)
310-311: Non-standard object identifiers forVoipRegion.swiftentries.The IDs
7AVR00012F5F5900002A6BDE,7AVR00022F5F5900002A6BDE, and7AVR00002F5F5900002A6BDE(also referenced at lines 649, 1162, 2136, 2419) contain non-hex characters (VR), whereas every other object in this pbxproj uses Xcode's standard 24-character hex identifiers. These appear to have been hand-authored rather than generated by Xcode.While Xcode tolerates these today, it's fragile: any future edit via Xcode (e.g., moving/renaming the file, or
pod installregenerating references) may produce duplicates or diffs that are hard to reconcile, and some pbxproj tooling assumes hex-only UUIDs. Consider regenerating these three IDs as standard 24-hex-char UUIDs to stay consistent with the rest of the project.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@ios/RocketChatRN.xcodeproj/project.pbxproj` around lines 310 - 311, The pbxproj contains non-standard IDs for VoipRegion.swift (currently referenced as 7AVR00012F5F5900002A6BDE, 7AVR00022F5F5900002A6BDE and 7AVR00002F5F5900002A6BDE); replace each of these with freshly generated 24-character lowercase hex IDs and update every occurrence consistently (the PBXBuildFile entries and any fileRef references that point to VoipRegion.swift) so the three references match Xcode's 24-hex format and remain consistent across the project file (search for VoipRegion.swift and the three non-hex IDs to locate all spots to change).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Nitpick comments:
In `@ios/RocketChatRN.xcodeproj/project.pbxproj`:
- Around line 310-311: The pbxproj contains non-standard IDs for
VoipRegion.swift (currently referenced as 7AVR00012F5F5900002A6BDE,
7AVR00022F5F5900002A6BDE and 7AVR00002F5F5900002A6BDE); replace each of these
with freshly generated 24-character lowercase hex IDs and update every
occurrence consistently (the PBXBuildFile entries and any fileRef references
that point to VoipRegion.swift) so the three references match Xcode's 24-hex
format and remain consistent across the project file (search for
VoipRegion.swift and the three non-hex IDs to locate all spots to change).
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: d5f41d27-55ac-4e92-8160-3b3cf8cf336d
📒 Files selected for processing (4)
ios/AppDelegate.swiftios/Libraries/AppDelegate+Voip.swiftios/Libraries/VoipRegion.swiftios/RocketChatRN.xcodeproj/project.pbxproj
🚧 Files skipped from review as they are similar to previous changes (3)
- ios/Libraries/AppDelegate+Voip.swift
- ios/Libraries/VoipRegion.swift
- ios/AppDelegate.swift
📜 Review details
🧰 Additional context used
🧠 Learnings (2)
📓 Common learnings
Learnt from: CR
Repo: RocketChat/Rocket.Chat.ReactNative PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-07T17:49:17.538Z
Learning: Applies to app/lib/services/voip/**/*.{ts,tsx} : Implement VoIP with WebRTC peer-to-peer audio calls in app/lib/services/voip/ using Zustand stores instead of Redux, with native CallKit (iOS) and Telecom (Android) integration; keep VoIP and VideoConf separate
📚 Learning: 2026-04-07T17:49:17.538Z
Learnt from: CR
Repo: RocketChat/Rocket.Chat.ReactNative PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-07T17:49:17.538Z
Learning: Applies to app/lib/services/voip/**/*.{ts,tsx} : Implement VoIP with WebRTC peer-to-peer audio calls in app/lib/services/voip/ using Zustand stores instead of Redux, with native CallKit (iOS) and Telecom (Android) integration; keep VoIP and VideoConf separate
Applied to files:
ios/RocketChatRN.xcodeproj/project.pbxproj
|
iOS Build Available Rocket.Chat Experimental 4.72.0.108575 |
Proposed changes
iOS-only: surface incoming Rocket.Chat VoIP calls in Phone.app Recents by enabling CallKit's
includesCallsInRecentsand moving CallKit setup fromindex.jstoAppDelegate.swift(native source of truth). Gate every CallKit touchpoint behindVoipRegion.isChina()— a new Swift helper that iterates all cellular subscribers for MCC=460 and falls back toNSLocale.current.regionCodeon no-SIM devices — to satisfy MIIT compliance on any CN SIM.ios/Libraries/VoipRegion.swift(new) — dual-SIM aware CN detectionios/AppDelegate.swift— expandedRNCallKeep.setupwithincludesCallsInRecents: true, region-gatedios/Libraries/AppDelegate+Voip.swift— guardsprepareIncomingCall+reportNewIncomingCall; PushKitcompletion()still fires on the gated pathios/RocketChatRN.xcodeproj/project.pbxproj— registers the new Swift file in both targetsNo Android changes. No manifest, entitlement, or store-review surface changed. One-commit revertable.
Issue(s)
https://rocketchat.atlassian.net/browse/VMUX-26
How to test or reproduce
Screenshots
N/A — native platform surface; no in-app UI change.
Types of changes
Checklist
Further comments
Why native-owned setup over the JS
RNCallKeep.setupbranch: CallKit is a platform lifecycle concern; keeping config in Swift eliminates a JS↔native round-trip at every cold start for a boot-time constant.RNCallKeep.setupbefore the RN bridge is ready is safe — it only mutates theCXProviderConfigurationsingleton and is bridge-independent.Why guarding
prepareIncomingCalltoo: it transitively instantiatesCXCallObserver(VoipService.swift:149), so it is itself a CallKit surface and must live inside the MIIT gate.Deferred: outgoing-call Recents entries (
RNCallKeep.startCallinjection site unresolved alongside theIClientMediaCalltrace).Platform asymmetry tradeoff: iOS setup now lives in
AppDelegate, Android setup remains inindex.js. Accepted for native ownership; future readers greppingRNCallKeep.setupmust check both sites.Summary by CodeRabbit
New Features
Improvements