chore: backport #2441, #2450, #2453, #2448, #2460, #2427 and #2465 to v1 - #2468
Conversation
Fixes a race where join() could keep retrying after leave(), causing a call to rejoin after the user had left. Stops pending join setup and retries when a leave occurs. Normal retries and explicitly joining again still work. Adds regression coverage for leaving during setup, an active join attempt, and retry delays. 🎫 Ticket: https://linear.app/stream/issue/XYZ-123 📑 Docs: https://github.com/GetStream/docs-content/pull/<id> <!-- This is an auto-generated comment: release notes by coderabbit.ai --> * **Bug Fixes** * Improved call lifecycle handling when leaving during an in-progress join or reconnect. * Prevented interrupted operations from continuing setup, retries, reconnection, media setup, or connection work. * Ensured interrupted calls remain in the left state without creating or restoring call sessions. * Prevented late responses and failures from triggering unwanted call updates or cleanup. * Preserved normal retry, error handling, and rejoining behavior when no leave interruption occurs. * **Tests** * Added coverage for leave races across joining, reconnecting, retries, teardown, and error scenarios. <!-- end of auto-generated comment: release notes by coderabbit.ai --> Backport note: adapted to `release-v1`, which does not have the `ClientState` refactor (#2422): `clientState` -> `clientStore` and `ClientState` -> `StreamVideoWriteableStateStore`. No behaviour change. (cherry picked from commit 1936cb5)
…#2450) Replaced RTCView with new camera preview view for outgoing call component 🎫 Ticket: https://linear.app/stream/issue/RN-427/outgoing-call-component-fix <!-- This is an auto-generated comment: release notes by coderabbit.ai --> * **Bug Fixes** * Improved the outgoing call screen’s camera preview behavior by reflecting the current camera mute state immediately. * Updated the preview presentation for better consistency with the call lobby experience. <!-- end of auto-generated comment: release notes by coderabbit.ai --> Backport note: adapted to `release-v1`, which does not have the shared i18n runtime (#2436): `useI18n` keeps coming from `@stream-io/video-react-bindings` instead of `../../../i18n`. No behaviour change. (cherry picked from commit e8e360b)
… Telecom address (#2453) ### 💡 Overview On ColorOS / Realme / Oppo devices, registering a Telecom call whose address has a null scheme crashes `system_server` and reboots the device (`PhoneNumberUtilsExtImpl.getNumberFromIntent` NPE → `UserCallIntentProcessor.blockAndLaunchSystemDialer`). callingx built the address with `String.toUri()` from an opaque Stream user id, which parses with `scheme == null`. Reproduced with `enableOngoingCalls: true`; Pixel/Samsung are unaffected. `main` (1.0.0-beta) still ships this. This PR: 1. Cherry-picks the contributor's fix from #2452 (authorship preserved), which introduced `toTelecomAddress` and a `sip:` scheme. 2. Aligns the Android address with the Stream Android SDK: scheme = app package name, so the address is `<packageName>:<callId>`. ### 📝 Implementation notes **Why the package name instead of `sip:`.** Nothing displays the address for self-managed calls; Telecom only needs a non-null scheme. AOSP `PhoneNumberUtils.getNumberFromIntent` returns `null` for an unknown scheme, whereas `sip:`/`tel:` make Telecom treat the handle as a dialable number. This is exactly what the Android SDK does (`ServiceLauncher`: `"$appSchema:${callId.id}".toUri()`, with the demo app passing `TelecomConfig(context.packageName)`). The handle is always wrapped, never parsed: parsing would promote anything before a `:` in the handle to the scheme, and the address is never displayed or dialed. With every address carrying the package-name scheme, the `tel`/`mailto` `Person.setUri` branch in `CallNotificationManager` could no longer fire and is removed. Contact lookup for real phone numbers can return later as an explicit `handleType`-driven scheme (`tel:`/`mailto:`), alongside the UUID-based `ACTION_CALL_BACK` work for core-telecom 1.1 unified call history. **Per-platform handle, decided in JS.** `getCallingxCallArgs` in the RN SDK is the single source of truth for what each platform receives as `phoneNumber`; Kotlin only guarantees a non-null scheme. | SDK | Handle / address | | --- | --- | | stream-video-swift | `CXHandle(.generic, created_by_id)` — visible in Recents, used for call-back | | stream-video-android | `<packageName>:<callId>` — never displayed | | callingx iOS (unchanged) | `CXHandle(.generic, createdBy.id ?? displayName)` | | callingx Android (this PR) | `<packageName>:<callId>`; JS sends `call.id`, the FCM push path (no JS) uses `call_cid` after the `:` | The call type is stripped because `"default:abc".toUri()` would make `default` the scheme. **Side effect.** `CallNotificationManager.createPerson` uses `Person.setKey(address.toString())`, so the key moves from per-user to per-call. Harmless for CallStyle notifications. **Verification.** callingx Kotlin compile with zero warnings, `yarn lint:ci:packages`, callingx `typecheck`, `yarn test:react-native:sdk`. No Android test source set exists in callingx; on-device ColorOS verification with `enableOngoingCalls: true` is still pending, hence draft. **Out of scope.** The fate of #2452 on `release-v1` (merge as a 0.11.x hotfix or close in favour of this) is decided separately. <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **Bug Fixes** * Improved Android call addressing for devices requiring valid Telecom addresses. * Android Telecom routing now uses the call ID, while iOS continues using the caller handle for CallKit display and recents. * Standardized Android call handles as app-scoped Telecom addresses, including incoming calls and empty or scheme-based handles. * Android caller entries now use stable call keys without platform contact-URI lookup. * **Documentation** * Clarified `phoneNumber` handling and platform-specific call address behavior. <!-- end of auto-generated comment: release notes by coderabbit.ai --> --------- Co-authored-by: sujeet kumar <ksujeetkumar7678@gmail.com> (cherry picked from commit 22d18d8)
#2448) ### 💡 Overview On iOS, the native Picture in Picture window renders one participant's track, but the SFU was still asked for the dimensions of the hidden inline layout behind it instead of the actual PiP window size. This PR reports the actual window bounds to the SFU while PiP is on screen, stops the hidden inline views from overwriting them, and hands the demand back to the inline views when PiP ends. Android subscription behaviour is unchanged. ### 📝 Implementation notes **Why it needs coordination.** Each participant has one `videoDimension` / `screenShareDimension` slot per track type, and every mounted `TrackSubscriber` writes into it, last writer wins. The PiP window is one more writer for the same slot. **Native (`RTCViewPip.swift`, `RTCViewPipManager.mm`)** * New `onPiPBoundsChange` event with the laid out window size in logical points, truncated and deduplicated, wired through the existing `onSizeUpdate` of the PiP controller. * Controller callbacks check that they still belong to the current controller, and are cleared in `onCallClosed`, so a disposed controller cannot emit. **JS** * `iosPipTrack.ts`: one `BehaviorSubject` per `Call` instance holding the track the PiP window currently renders. `setIosPipTrack` clears the previous track's demand *before* flipping the subject, so inline views replay their cached layout synchronously and overwrite the clear. If no inline view is mounted, the clear stands and the SFU stops sending that track. * `TrackSubscriber`: on iOS, one extra `combineLatest` input gates writes. Inline subscribers stop writing while their track is owned by PiP; the PiP writer (`isPipWriter`) only writes while it owns the track. The writer releases ownership in a microtask so sibling inline views finish unmounting first on whole-screen teardown. * `RTCViewPipIOS`: feeds the bounds event into a `BehaviorSubject` and renders a `TrackSubscriber` with `isPipWriter` for the spotlight participant while PiP is active (never for the local participant). The native view is keyed per `Call` instance and the handlers are bound to that key, so events queued by a replaced call's controller are rejected. Lifecycle events are deduplicated before reaching `isInPiPMode$` and `onPiPChange`. * `CallContent`: renders `RTCViewPipIOS` on iOS only. **Tests** * `RTCViewPipIOS.test.tsx`: bounds win over hidden layout (start-first and bounds-first), demand transfer between participants and from camera to screen share, clearing a track without an inline view, local-only PiP, hand-back on unmount / `call.ended` / `LEFT`, teardown ordering, restart during a pending stop, per-`Call` isolation with the same cid, and rejection of stale events after call replacement. * `TrackSubscriber.test.tsx`: PiP writer follows publication and rejoin, inline visibility, and unchanged Android behaviour. **Validation** SDK lint, Prettier, spec type check, and the RN SDK test suite pass. Coverage of the touched files is unchanged. Physical-device validation is still pending. 🎫 Ticket: https://linear.app/stream/issue/XYZ-123 📑 Docs: https://github.com/GetStream/docs-content/pull/<id> <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **Bug Fixes** * iOS Picture-in-Picture now waits for the PiP window’s dimensions before requesting video quality, and updates sizing when the window is resized. * Video demand transfers more reliably between inline and PiP views when participants or shared content change. * Events from a closed or replaced PiP window no longer affect the active call. * PiP lifecycle callbacks now report state changes consistently after calls end or are replaced. * **Platform Compatibility** * The iOS-specific PiP view is rendered only on iOS when PiP is enabled. <!-- end of auto-generated comment: release notes by coderabbit.ai --> (cherry picked from commit b7e4165)
…m is attached (#2460) ### 💡 Overview Closes a gap where an E2EE publisher could put unencrypted media on the wire. The sender used to be created with its track and the encryptor attached only after an `await`, so another negotiation (ICE restart, a second publish) could send the track without a transform. If E2EE setup failed partway, the half-initialized sender stayed in the transceiver cache and a retry reused it unencrypted. ### 📝 Implementation notes - E2EE senders are created with no track (`addTransceiver(kind)`). `encrypt()` runs in the same task, then sender parameters, then `replaceTrack(track)`. The sender has no media until all of these succeed. - On any setup failure the sender is retired: removed from `TransceiverCache` (new `remove()`), stopped, and its clone released (not on React Native, where clones share the native source). A sender whose encryption succeeded but negotiation failed is kept and reused. - `EncryptionManager.pipe` marks a target only after `createEncodedStreams()` succeeds, so a failed attach can be retried and a successful one is never piped twice. - **Behavior change for custom `E2EEManager` implementations:** `encrypt()` now receives a sender whose `track` is `null`. Use the `codec` and `trackType` arguments instead of reading the track. Documented on the interface and in `SPEC.md`. - Removes the unused `e2ee/workerMessages.ts`. - Known follow-up: if an ICE restart happens while the first and only E2EE sender is still being set up, the restart has no tracks to announce and falls back to a rejoin. Nothing unencrypted is sent. - Tested with unit tests covering every failure point, the ICE-restart race and cleanup, plus a manual E2EE call in react-dogfood. 🎫 Ticket: https://linear.app/stream/issue/REACT-1182 📑 Docs: https://github.com/GetStream/docs-content/pull/<id> <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **Bug Fixes** * Improved reliability when starting encrypted audio and video publishing. Media is attached only after encryption setup succeeds, and failed setup is cleaned up so publishing can be retried. * Prevented incomplete encrypted media from being announced during connection recovery, reducing the chance of publishing interruptions or inconsistent media state. * Improved cleanup after encryption setup errors, helping preserve the original error and preventing failed publishing attempts from interfering with later retries. <!-- end of auto-generated comment: release notes by coderabbit.ai --> (cherry picked from commit 2419b01)
💡 Overview Adds end-to-end encryption to the React Native SDK. Encryption runs natively in @stream-io/react-native-webrtc, using the same wire format as the web and iOS SDKs, so encrypted calls interoperate across platforms. - EncryptionManager — native-backed implementation of the core E2EEManager interface, API-compatible with the web manager. - StreamVideoRN.setRingingCallLifecycleHooks() — ringing calls are joined by the SDK, not by app code, so there is no moment where the app can attach a manager before the join. These hooks are that moment, on every ringing path. - Dogfood: passphrase entry, lock badge, live re-keying, key-mismatch warning. 📝 Implementation notes - Call.ts: globalThis.streamRNVideoSDK bridge plus cancellation checks reusing leaveGeneration. No new Call fields; web and non-ringing calls are untouched. - Setup failure or the 5s timeout aborts the join and ends the ringing flow. Joining unencrypted on a call the user believes is private is the worse outcome. - One Call is one call flow — discard it after leave/cancel/failed join. Documented contract, not an enforced ban; inherited reuse behaviour and tests are untouched. - dispose() is mandatory on RN (no native detach, closing peer connections doesn't release it), and gated on teardown succeeding. webrtc PR: GetStream/react-native-webrtc#68 docs PR: GetStream/docs-content#1586 🎫 Ticket: https://linear.app/stream/issue/RN-434/ <!-- This is an auto-generated comment: release notes by coderabbit.ai --> - **New Features** - Added React Native end-to-end encryption with AES-GCM key management and status handling. - Added encryption key entry, mismatch notifications, and an encryption indicator during calls. - Encrypted deep links now preserve and apply meeting keys securely. - Added configurable ringing-call lifecycle hooks for call preparation and cleanup. - **Bug Fixes** - Improved cancellation and cleanup when calls are left during joining. - Failed push-call joins now correctly report failure to the calling platform. - **Documentation** - Added guidance and testing documentation for React Native encryption and sample-app workflows. <!-- end of auto-generated comment: release notes by coderabbit.ai --> --------- Co-authored-by: GitHub Actions Bot <> Co-authored-by: Gabriel Donadel Dall'Agnol <donadeldev@gmail.com> Co-authored-by: Oliver Lazoroski <oliver.lazoroski@gmail.com> Backport note: adapted to `release-v1`, which does not have the `ClientState` refactor (#2422) or the shared i18n runtime (#2436). - `clientState` -> `clientStore` and `ClientState` -> `StreamVideoWriteableStateStore` in `Call.ts` and the two new ringing lifecycle test files. - `ringingJoinIntegration.test.ts` hands the Call `client['writeableStateStore']`: v1 still has two stores, and `client.state` is the read-only one. - RN SDK peer ranges for the sibling packages stay `>=0.1.0`; only the `@stream-io/react-native-webrtc` range and dev dependency change. - Dogfood: adds a v1 `hooks/useAppI18n.ts` that maps the `t(key, fallback, options)` calls to v1's `t(key, options)`. `CallErrorComponent` and `ParticipantsInfoListModal` keep their v1 strings and carry only this PR's own change. - `yarn.lock` regenerated from v1's with `yarn install --mode=update-lockfile`, not merged from `main`. (cherry picked from commit fd70b35)
Bump `@stream-io/react-native-webrtc` to `145.4.1` and remove the alpha peer dependency range from the React Native SDK. - React Native SDK: peer dependency is now `^145.4.1`. The `>=145.4.0-alpha.1` range is removed. - Helper packages (callingx, noise-cancellation, video-filters): dev dependency bumped to `145.4.1`. Peer dependency stays at `^145.3.1`. - Sample apps (dogfood, expo-video-sample, ringing-tutorial): updated to `145.4.1`. 🎫 Ticket: https://linear.app/stream/issue/RN-434 📑 Docs: N/A <!-- This is an auto-generated comment: release notes by coderabbit.ai --> * **Chores** * Updated the React Native WebRTC version used by calling, noise cancellation, video filters, and sample apps to 145.4.1. * The React Native SDK now uses and supports the 145.4.1 release, replacing its earlier and prerelease versions. Included sample apps and development setups are aligned with this version. <!-- end of auto-generated comment: release notes by coderabbit.ai --> Backport note: the version bump is re-applied to `release-v1`'s own `package.json` files, whose sibling peer ranges differ from `main`'s, and `yarn.lock` is regenerated from v1's with `yarn install --mode=update-lockfile` rather than merged from `main`. (cherry picked from commit 891c4c9)
|
Important Review skippedAuto reviews are disabled on base/target branches other than the default branch. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Advanced Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
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 |
Bundle sizeBuilt package output. Sizes in KB; delta vs
|
💡 Overview
Backports the merged
backport-v1PRs torelease-v1, in merge order. Every commit keeps its original author and carries a(cherry picked from commit ...)trailer pointing at the commit onmain.fix(client): abandon retries when leave supersedes join- adaptedfix: replaced RTCView with camera preview for outgoing call component- adaptedfix(react-native-callingx): use <packageName>:<callId> as the Android Telecom address- cleanfix(react-native): iOS Picture in Picture window size must be reported- cleanfix(client): never publish unencrypted media before the E2EE transform is attached- cleanfeat(rn): add end-to-end encryption support- adaptedchore(react-native): bump webrtc to 145.4.1- re-applied,yarn.lockregeneratedSkipped: #2456. Its changes are already carried by the #2427 backport; trial-picking it on top of this branch produces no changes.
Important
Merge with Rebase and merge, not squash, and only once every check below is green.
release-v1has no required status checks, so the button works while CI is still running. Squashing replaces the commit messages with this description and drops the authorship and cherry-pick trailers.Note
#2465 raises the RN SDK's
@stream-io/react-native-webrtcpeer range to^145.4.1, matchingmain. Apps onlatestwill need to upgrade WebRTC.📝 Implementation notes
release-v1has neither #2422 (singleClientState) nor #2436 (shared i18n runtime), and neither is flagged for backport. The adapted picks rewrite against the APIs v1 still has instead of pulling those in; each commit body records exactly what changed.clientState->clientStore,ClientState->StreamVideoWriteableStateStore. One feat(rn): add end-to-end encryption support #2427 test hands theCallclient['writeableStateStore'], because v1 still has two stores andclient.stateis the read-only one.useI18nstays on@stream-io/video-react-bindings.>=0.1.0; only the WebRTC range and dev dependency change.hooks/useAppI18n.tsthat mapsmain'st(key, fallback, options)calls onto v1'st(key, options).CallErrorComponentandParticipantsInfoListModalkeep their v1 strings and carry only feat(rn): add end-to-end encryption support #2427's own change.package.jsonfiles rather than merged frommain.yarn.lockis regenerated from v1's copy withyarn install --mode=update-lockfilein both feat(rn): add end-to-end encryption support #2427 and chore(react-native): bump webrtc to 145.4.1 #2465.Podfile.lockis left as picked; CI'spod installrewrites it.✅ Verification (local)
release-v1inclient,react-native-sdk,react-native-callingx,noise-cancellation-react-native, and the RN dogfood, with the workspace packages resolved to this branch's v1 sources.clienttests: 1248 passed.Call.test.ts,StreamVideoClient.test.tsandStreamVideoClient.ringing.test.tsneedSTREAM_SECRETand fail to load locally, same as onrelease-v1; they run here in CI.react-native-sdktests: 172 passed (20 suites), against this branch's built v1 client and bindings.🎫 Ticket: https://linear.app/stream/issue/REACT-1190