Skip to content

feat(voip): iOS CallKit Recents integration#7152

Open
diegolmello wants to merge 1 commit intofeat.voip-lib-newfrom
feature/os-phone-ios
Open

feat(voip): iOS CallKit Recents integration#7152
diegolmello wants to merge 1 commit intofeat.voip-lib-newfrom
feature/os-phone-ios

Conversation

@diegolmello
Copy link
Copy Markdown
Member

@diegolmello diegolmello commented Apr 14, 2026

Proposed changes

iOS-only: surface incoming Rocket.Chat VoIP calls in Phone.app Recents by enabling CallKit's includesCallsInRecents and moving CallKit setup from index.js to AppDelegate.swift (native source of truth). Gate every CallKit touchpoint behind VoipRegion.isChina() — a new Swift helper that iterates all cellular subscribers for MCC=460 and falls back to NSLocale.current.regionCode on no-SIM devices — to satisfy MIIT compliance on any CN SIM.

  • ios/Libraries/VoipRegion.swift (new) — dual-SIM aware CN detection
  • ios/AppDelegate.swift — expanded RNCallKeep.setup with includesCallsInRecents: true, region-gated
  • ios/Libraries/AppDelegate+Voip.swift — guards prepareIncomingCall + reportNewIncomingCall; PushKit completion() still fires on the gated path
  • ios/RocketChatRN.xcodeproj/project.pbxproj — registers the new Swift file in both targets

No 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

  1. Non-CN iOS 15/17 device → place a Rocket.Chat VoIP call → after hangup, Phone.app Recents shows the entry with caller name.
  2. Device with any SIM reporting MCC=460 → no CallKit UI, no Recents entry, push completion still called (no system termination).
  3. iPad/no-SIM with region=CN in Settings → gated via locale fallback.
  4. iPad/no-SIM with region=US → not gated; Recents entry present.

Screenshots

N/A — native platform surface; no in-app UI change.

Types of changes

  • New feature (non-breaking change which adds functionality)

Checklist

  • I have read the CONTRIBUTING doc
  • I have signed the CLA
  • Lint and unit tests pass locally with my changes
  • I have added tests that prove my fix is effective or that my feature works (if applicable) — native CallKit behavior requires device verification
  • I have added necessary documentation (if applicable)
  • Any dependent changes have been merged and published in downstream modules

Further comments

Why native-owned setup over the JS RNCallKeep.setup branch: 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.setup before the RN bridge is ready is safe — it only mutates the CXProviderConfiguration singleton and is bridge-independent.

Why guarding prepareIncomingCall too: it transitively instantiates CXCallObserver (VoipService.swift:149), so it is itself a CallKit surface and must live inside the MIIT gate.

Deferred: outgoing-call Recents entries (RNCallKeep.startCall injection site unresolved alongside the IClientMediaCall trace).

Platform asymmetry tradeoff: iOS setup now lives in AppDelegate, Android setup remains in index.js. Accepted for native ownership; future readers grepping RNCallKeep.setup must check both sites.

Summary by CodeRabbit

  • New Features

    • Implemented region-aware calling functionality for enhanced compatibility
    • Incoming call handling now adapts based on device region and cellular provider
    • Calling interface automatically optimizes settings based on location
  • Improvements

    • Better VoIP service reliability across different geographic regions

@diegolmello diegolmello changed the title feat(voip): iOS CallKit Recents integration with MIIT gate feat(voip): iOS CallKit Recents integration Apr 14, 2026
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 14, 2026

Walkthrough

These 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

Cohort / File(s) Summary
CallKeep Configuration
ios/AppDelegate.swift, ios/Libraries/AppDelegate+Voip.swift
Added conditional region checks to gate CallKeep setup and incoming call reporting. CallKeep configuration now expands to include supportsVideo, maximumCallGroups, maximumCallsPerCallGroup, and includesCallsInRecents when not in China region.
Region Detection Utility
ios/Libraries/VoipRegion.swift
New file defining VoipRegion enum with isChina() static method that detects China region via cellular provider country code "460" or system locale region code "CN".
Build Configuration
ios/RocketChatRN.xcodeproj/project.pbxproj
Added build file references and source phase entries to include VoipRegion.swift in compilation for both RocketChatRN and Rocket.Chat targets.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~12 minutes

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 33.33% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat(voip): iOS CallKit Recents integration' directly and clearly describes the main change: enabling CallKit recents functionality on iOS to surface incoming VoIP calls in the OS Phone app.
Linked Issues check ✅ Passed The PR fully implements the coding requirements from VMUX-26: enables incoming VoIP calls in OS Phone app recents via CallKit integration on iOS and implements region-based gating to exclude China devices.
Out of Scope Changes check ✅ Passed All changes are directly aligned with objectives: VoipRegion helper for China detection, CallKit setup with includesCallsInRecents gating, and AppDelegate modifications for region-aware call handling. No unrelated changes detected.

✏️ 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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

📥 Commits

Reviewing files that changed from the base of the PR and between 2143aba and 36df4ae.

📒 Files selected for processing (4)
  • ios/AppDelegate.swift
  • ios/Libraries/AppDelegate+Voip.swift
  • ios/Libraries/VoipRegion.swift
  • ios/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.swift
  • ios/Libraries/AppDelegate+Voip.swift
  • ios/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.swift is properly referenced and compiled in both app targets, and added to the Libraries group.

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 prepareIncomingCall and reportNewIncomingCall while still reaching completion() matches the intended CN behavior.

Comment thread ios/Libraries/VoipRegion.swift
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.
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

📥 Commits

Reviewing files that changed from the base of the PR and between 36df4ae and 337827f.

📒 Files selected for processing (7)
  • app/lib/services/voip/MediaSessionInstance.ts
  • app/lib/services/voip/MediaSessionStore.ts
  • ios/AppDelegate.swift
  • ios/Libraries/AppDelegate+Voip.swift
  • ios/Libraries/VoipRegion.swift
  • ios/RocketChatRN.xcodeproj/project.pbxproj
  • packages/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-config base 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'.

Comment thread app/lib/services/voip/MediaSessionStore.ts Outdated
@diegolmello diegolmello force-pushed the feature/os-phone-ios branch from a5e7355 to 9736652 Compare April 20, 2026 14:49
@diegolmello diegolmello requested a deployment to approve_e2e_testing April 20, 2026 14:49 — with GitHub Actions Waiting
@diegolmello diegolmello requested a deployment to official_android_build April 20, 2026 14:53 — with GitHub Actions Waiting
@diegolmello diegolmello requested a deployment to experimental_android_build April 20, 2026 14:53 — with GitHub Actions Waiting
@diegolmello diegolmello temporarily deployed to experimental_ios_build April 20, 2026 14:53 — with GitHub Actions Inactive
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
ios/RocketChatRN.xcodeproj/project.pbxproj (1)

310-311: Non-standard object identifiers for VoipRegion.swift entries.

The IDs 7AVR00012F5F5900002A6BDE, 7AVR00022F5F5900002A6BDE, and 7AVR00002F5F5900002A6BDE (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 install regenerating 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

📥 Commits

Reviewing files that changed from the base of the PR and between 337827f and 9736652.

📒 Files selected for processing (4)
  • ios/AppDelegate.swift
  • ios/Libraries/AppDelegate+Voip.swift
  • ios/Libraries/VoipRegion.swift
  • ios/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

@github-actions
Copy link
Copy Markdown

iOS Build Available

Rocket.Chat Experimental 4.72.0.108575

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant