Skip to content

Conversation

@gabriellsh
Copy link
Member

@gabriellsh gabriellsh commented Aug 14, 2025

Proposed changes (including videos or screenshots)

Issue(s)

VAI-74
VAI-111

Steps to test or reproduce

Further comments

Summary by CodeRabbit

  • New Features

    • Unified voice-call architecture and widget; start voice calls from sidebar, navbar, DMs, and user cards when permitted.
  • Improvements

    • Navbar consolidated to a single call action; device-permission prompt and improved device settings flow; keypad '*' enlarged; toggle buttons show contextual titles (Mute/Unmute, Hold/Resume); transfer flow now validates selection and shows peer info; call sound handling added.
  • Accessibility

    • Peer search exposes field-level errors and improved ARIA relationships.
  • Localization

    • Added new call-related translation keys.
  • Chores

    • Package bumps and dependency additions.

@dionisio-bot
Copy link
Contributor

dionisio-bot bot commented Aug 14, 2025

Looks like this PR is ready to merge! 🎉
If you have any trouble, please check the PR guidelines

@changeset-bot
Copy link

changeset-bot bot commented Aug 14, 2025

🦋 Changeset detected

Latest commit: f657c2d

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 41 packages
Name Type
@rocket.chat/meteor Minor
@rocket.chat/i18n Minor
@rocket.chat/ui-voip Major
@rocket.chat/mock-providers Patch
@rocket.chat/ui-contexts Major
@rocket.chat/web-ui-registration Major
@rocket.chat/omnichannel-transcript Patch
@rocket.chat/omnichannel-services Patch
@rocket.chat/fuselage-ui-kit Major
@rocket.chat/ui-client Major
@rocket.chat/uikit-playground Patch
@rocket.chat/gazzodown Major
@rocket.chat/livechat Patch
@rocket.chat/ui-avatar Major
@rocket.chat/ui-video-conf Major
@rocket.chat/queue-worker Patch
@rocket.chat/core-typings Minor
@rocket.chat/rest-typings Minor
@rocket.chat/api-client Patch
@rocket.chat/apps Patch
@rocket.chat/core-services Patch
@rocket.chat/cron Patch
@rocket.chat/ddp-client Patch
@rocket.chat/freeswitch Patch
@rocket.chat/http-router Patch
@rocket.chat/model-typings Patch
@rocket.chat/account-service Patch
@rocket.chat/authorization-service Patch
@rocket.chat/ddp-streamer Patch
@rocket.chat/presence-service Patch
@rocket.chat/stream-hub-service Patch
@rocket.chat/license Patch
@rocket.chat/media-calls Patch
@rocket.chat/pdf-worker Patch
@rocket.chat/presence Patch
rocketchat-services Patch
@rocket.chat/models Patch
@rocket.chat/network-broker Patch
@rocket.chat/omni-core-ee Patch
@rocket.chat/instance-status Patch
@rocket.chat/omni-core Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@gabriellsh gabriellsh changed the base branch from develop to feat/media-calls August 14, 2025 17:40
@codecov
Copy link

codecov bot commented Aug 26, 2025

Codecov Report

❌ Patch coverage is 61.06195% with 88 lines in your changes missing coverage. Please review.
✅ Project coverage is 67.31%. Comparing base (ddfb304) to head (f657c2d).
⚠️ Report is 1 commits behind head on develop.

Additional details and impacted files

Impacted file tree graph

@@             Coverage Diff             @@
##           develop   #36717      +/-   ##
===========================================
- Coverage    67.35%   67.31%   -0.05%     
===========================================
  Files         3339     3337       -2     
  Lines       113181   113294     +113     
  Branches     20539    20557      +18     
===========================================
+ Hits         76234    76264      +30     
- Misses       34344    34428      +84     
+ Partials      2603     2602       -1     
Flag Coverage Δ
e2e 57.01% <77.02%> (+0.10%) ⬆️
unit 71.16% <53.28%> (-0.14%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@gabriellsh gabriellsh force-pushed the feat/mediaCallClient branch 2 times, most recently from d31db5c to 6256fd8 Compare September 4, 2025 14:29
@pierre-lehnen-rc pierre-lehnen-rc changed the base branch from feat/media-calls to feat/media-calls-backend September 5, 2025 17:12
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Sep 10, 2025

Walkthrough

Replaces legacy VoIP pieces with a new media-call stack: adds MediaCallProvider and v2 media-call modules (context, provider, session, streams, sounds, actions), integrates useMediaCallAction across navbar/sidebar/user card/room toolbox, revises device permission/picker and transfer/keypad/toggle flows, updates i18n, and removes the old NavBar VoIP dialer item.

Changes

Cohort / File(s) Summary
Removal: VoIP Dialer Item
apps/meteor/client/NavBarV2/NavBarVoipGroup/NavBarItemVoipDialer.tsx
Deleted NavBarItemVoipDialer component and its props/type usage.
Navbar & Sidebar Integration
apps/meteor/client/NavBarV2/NavBarVoipGroup/NavBarVoipGroup.tsx, apps/meteor/client/sidebar/header/hooks/useUserMenu.tsx
Replace VOIP hooks with useMediaCallAction; render a single dynamic NavBarItem and add a single media-call item in user menu when available.
Providers: Meteor → MediaCall
apps/meteor/client/providers/MeteorProvider.tsx, apps/meteor/client/providers/MediaCallProvider.tsx
Swap VoipProvider for MediaCallProvider; add MediaCallProvider that gates behavior by license/permissions and exposes unlicensed/unauthorized context shapes.
Room Action Hooks
apps/meteor/client/hooks/roomActions/useMediaCallRoomAction.ts, apps/meteor/client/ui.ts
Add useMediaCallRoomAction for single-peer room actions and register it in roomActionHooks (replaces legacy voice-room hook).
User Info Actions & Types
apps/meteor/client/views/room/hooks/useUserInfoActions/actions/useUserMediaCallAction.ts, apps/meteor/client/views/room/hooks/useUserInfoActions/useUserInfoActions.ts, apps/meteor/client/views/room/UserCard/UserCardWithData.tsx
Add useUserMediaCallAction; introduce disabled?: boolean on UserInfoAction types and propagate disabled to UserCardAction; replace prior voipCall usage with userMediaCall.
ui-voip: package exports & dependency
packages/ui-voip/package.json, packages/ui-voip/src/index.ts, packages/ui-voip/src/v2/index.ts
Add dependency @rocket.chat/media-signaling; export MediaCallProvider, useMediaCallExternalContext, useMediaCallAction, re-export MediaCallContext/PeerInfo.
ui-voip v2: Context & Autocomplete
packages/ui-voip/src/v2/MediaCallContext.ts, packages/ui-voip/src/v2/components/PeerAutocomplete.tsx
Introduce union context types (external/unauthorized/unlicensed), useMediaCallExternalContext, stricter useMediaCallContext guards, change usePeerAutocomplete signature, add error/ARIA wiring to PeerAutocomplete.
ui-voip v2: Provider, Session & Streams
packages/ui-voip/src/v2/MediaCallProvider.tsx, packages/ui-voip/src/v2/useMediaSessionInstance.ts, packages/ui-voip/src/v2/useMediaSession.ts, packages/ui-voip/src/v2/useMediaStream.ts, packages/ui-voip/src/v2/useCallSounds.ts
Add full MediaCallProvider implementation, session instance/store management, media session hook with actions, media stream binding hook, and call-sounds hook.
ui-voip v2: Actions/Mapping
packages/ui-voip/src/v2/useMediaCallAction.ts
Add useMediaCallAction hook returning title/icon/action per media-call state (returns undefined for 'unauthorized').
Device permission & picker
packages/ui-voip/src/hooks/useDevicePermissionPrompt.tsx, packages/ui-voip/src/v2/components/DevicePicker.tsx
Add stopTracks, PermissionRequestCancelledCallRejectedError, useDevicePermissionPrompt2; rework DevicePicker to prompt on open and centralize onAction logic.
Transfer flow & Keypad / Controls
packages/ui-voip/src/v2/TransferModal.tsx, packages/ui-voip/src/v2/components/Keypad/Key.tsx, packages/ui-voip/src/v2/components/Keypad/Keypad.tsx, packages/ui-voip/src/v2/components/ToggleButton.tsx, packages/ui-voip/src/v2/views/OngoingCall.tsx, packages/ui-voip/src/v2/components/PeerAutocomplete.tsx, packages/ui-voip/src/v2/components/ToggleButton.stories.tsx
TransferModal confirms with a peer object and handles inline errors; Key adds large prop used for *; ToggleButton supports titles prop and OngoingCall toggles receive titles; PeerAutocomplete accepts error and ARIA ids.
i18n additions
packages/i18n/src/locales/en.i18n.json
Add new keys for dialpad/device/call states and per-user call variants.
UserCard tweaks
apps/meteor/client/views/room/UserCard/UserCardWithData.tsx
Wire action disabled through to UserCardAction.
Changeset
.changeset/sweet-ghosts-teach.md
Bump minor versions and add changelog entry describing the new voice call architecture and widget updates.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor U as User
  participant App as MeteorProvider
  participant MCP as MediaCallProvider
  participant Ctx as MediaCallContext
  participant UI as NavBar/Sidebar/UserCard

  App->>MCP: render children
  MCP->>MCP: check license & permissions
  alt unlicensed
    MCP-->>Ctx: provide state='unlicensed' and onToggleWidget->showWarning
  else unauthorized
    MCP-->>Ctx: provide state='unauthorized' (no handlers)
  else licensed & allowed
    MCP->>Ctx: provide full MediaCallContext (state, peerInfo, handlers)
  end

  UI->>Ctx: call useMediaCallAction(callee?)
  alt undefined (unauthorized)
    Ctx-->>UI: undefined
    UI-->>U: no call item shown
  else available
    Ctx-->>UI: {title, icon, action}
    U->>UI: click call item
    UI->>Ctx: action(callee?)
  end
Loading
sequenceDiagram
  autonumber
  participant Ctx as MediaCallContext
  participant Hook as useMediaCallAction
  participant Sess as useMediaSession
  participant Widget as MediaCallWidget

  Hook->>Ctx: read {state, peerInfo, onEndCall, onToggleWidget}
  alt ongoing/calling/ringing with peerInfo
    Hook-->>Widget: {title=per-state user/title, icon='phone-off', action=onEndCall}
  else callee provided
    Hook-->>Widget: {title='Voice_call__user_', icon='phone', action=onToggleWidget(callee)}
  else default
    Hook-->>Widget: {title='New_voice_call', icon='dialpad', action=onToggleWidget()}
  end

  Widget->>Ctx: invoke action() -> Ctx performs start/accept/end/select etc.
  Ctx->>Sess: call session APIs (startCall, endCall, acceptCall, sendTone, changeDevice)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60–90 minutes

Possibly related PRs

Suggested reviewers

  • KevLehman
  • tassoevan

Poem

A whisker twitches, a dialpad hums,
New channels open, old bell drums.
Licenses checked and permissions clear,
I hop to call — a voice draws near.
🐇☎️ Hop, ring — a tiny cheer!

Pre-merge checks and finishing touches

❌ Failed checks (2 inconclusive)
Check name Status Explanation Resolution
Linked Issues Check ❓ Inconclusive The changes implement a new client/provider surface (adds MediaCallProvider, replaces VoipProvider in MeteorProvider, introduces useMediaSessionInstance/useMediaCallAction/useMediaSession and updates room/user action hooks) which maps to the high-level goal in VAI-74; however [VAI-111] contains no acceptance criteria or details in the linked data so I cannot fully verify compliance for that issue. Please provide explicit acceptance criteria and QA/test steps for VAI-74 and the missing details for [VAI-111], and annotate which files/changes map to each criterion so reviewers can validate coverage and expected behavior.
Out of Scope Changes Check ❓ Inconclusive Most edits align with implementing the new media-call architecture (providers, session hooks, ui-voip v2 exports and wiring), but the PR also contains multiple UI/API adjustments (ToggleButton titles, Key.large prop, TransferModal signature change, PeerAutocomplete error prop, UserCard disabled prop, etc.) that could be incidental to the core provider work; because the linked issues lack detailed scope, I cannot conclusively determine whether those are out-of-scope. Author should document which changes are required for the provider integration versus incidental UX/API refactors, call out breaking public API changes (e.g., TransferModal signature and exported types), and consider splitting non-essential UI tweaks into separate PRs or adding migration notes for reviewers.
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The title "feat: Media Call Client" is short, focused, and accurately highlights the primary change (introducing the media call client/provider and related UI integration), making it clear and scannable for reviewers and history.
Docstring Coverage ✅ Passed No functions found in the changes. Docstring coverage check skipped.
✨ Finishing touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/mediaCallClient

📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Jira integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 8a3e042 and f0ff773.

📒 Files selected for processing (2)
  • packages/ui-voip/src/v2/components/Keypad/Key.tsx (3 hunks)
  • packages/ui-voip/src/v2/components/Keypad/Keypad.tsx (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • packages/ui-voip/src/v2/components/Keypad/Key.tsx
  • packages/ui-voip/src/v2/components/Keypad/Keypad.tsx
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: 📦 Build Packages
  • GitHub Check: CodeQL-Build
  • GitHub Check: CodeQL-Build

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
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: 17

🧹 Nitpick comments (53)
packages/ui-voip/src/v2/components/Widget/WidgetHandle.tsx (1)

28-29: A11y: hide decorative icon from screen readers

The meatballs icon is purely visual; mark it decorative.

-			<Icon color='info' name='stacked-meatballs' size='x20' />
+			<Icon aria-hidden='true' color='info' name='stacked-meatballs' size='x20' />
packages/ui-voip/src/v2/components/Widget/Widget.tsx (3)

36-38: Call boundingRef(null) on unmount and guard for SSR

The ref callback likely installs DOM listeners; without cleanup this can leak. Also guard for non‑browser environments.

-	useLayoutEffect(() => {
-		boundingRef(document.body);
-	}, [boundingRef]);
+	useLayoutEffect(() => {
+		if (typeof document === 'undefined') {
+			return;
+		}
+		boundingRef(document.body);
+		return () => boundingRef(null);
+	}, [boundingRef]);

3-6: Memoize context value to avoid unnecessary re-renders of all consumers

Provider currently creates a new object every render; memoize it since the ref callbacks are stable.

-import { ComponentProps, useLayoutEffect } from 'react';
+import { ComponentProps, useLayoutEffect, useMemo } from 'react';
@@
-	return (
-		<DragContext.Provider value={{ draggableRef, boundingRef, handleRef }}>
+	const value = useMemo(() => ({ draggableRef, boundingRef, handleRef }), [draggableRef, boundingRef, handleRef]);
+	return (
+		<DragContext.Provider value={value}>

Also applies to: 41-45


29-31: children is redundant in WidgetProps

ComponentProps<typeof WidgetBase> already includes children; you can drop the explicit field.

-type WidgetProps = {
-	children: React.ReactNode;
-} & ComponentProps<typeof WidgetBase>;
+type WidgetProps = ComponentProps<typeof WidgetBase>;
packages/ui-voip/src/v2/useKeypad.tsx (1)

33-36: Toggle logic is clean; minor naming nit

Optional: expose isOpen in the hook result to allow consumers to reflect UI state without re-deriving.

 return {
   element,
-  buttonProps: {
+  isOpen: open,
+  buttonProps: {
     title: open ? t('Close_dialpad') : t('Open_dialpad'),
     onClick: () => setOpen((open) => !open),
-  },
+  },
 };
apps/meteor/client/NavBarV2/NavBarVoipGroup/NavBarVoipGroup.tsx (2)

2-8: Remove commented imports before merge

Dead commented imports accumulate noise. If feature gating is gone, delete; if temporary, add TODO with tracking issue.


21-23: OK to drop toggler if replaced

If the toggler UI was superseded by the dialer action, remove the commented JSX.

packages/ui-voip/src/providers/useMediaStream.ts (2)

20-23: Minor: ignore play() promise and add playsInline for mobile

Optional hardening: prefix with void to signal intentional ignore; playsInline helps iOS.

- node.srcObject = remoteStream;
- node.play().catch((error) => {
+ node.srcObject = remoteStream;
+ node.playsInline = true;
+ void node.play().catch((error) => {
   console.error('MediaCall: useMediaStream - Error playing media stream', error);
 });

25-29: Cleanup order is fine; consider guarding against stale element

If multiple mounts happen quickly, ensure you pause/clear only the element you set.

- return () => {
-   actualRef.current = null;
-   node.pause();
-   node.srcObject = null;
- };
+ return () => {
+   if (actualRef.current === node) {
+     actualRef.current = null;
+     node.pause();
+     node.srcObject = null;
+   }
+ };
packages/ui-contexts/src/hooks/useMediaDevicePermission.ts (1)

27-33: Docstring now mismatches capability (audio‑only wording vs. generic constraints).

requestDevice can accept video constraints now. Either restrict the accepted constraints to audio or update the JSDoc to reflect broader capability.

packages/ui-voip/src/v2/components/ActionButton.tsx (1)

5-10: Type onClick to match IconButton’s handler.

Use React’s MouseEventHandler to allow callers to access the event.

-import { ComponentProps, forwardRef } from 'react';
+import { ComponentProps, forwardRef, type MouseEventHandler } from 'react';
@@
 type ActionButtonProps = {
 	label: string;
 	icon: Keys;
 	disabled?: boolean;
-	onClick?: () => void;
+	onClick?: MouseEventHandler<HTMLButtonElement>;
 } & Omit<ComponentProps<typeof IconButton>, 'icon' | 'title' | 'aria-label' | 'disabled' | 'onClick'>;
packages/ui-voip/src/hooks/useDevicePermissionPrompt.tsx (6)

50-54: LGTM: central helper to stop tracks.

Good utility; consider reusing it below to avoid duplicate logic.


63-66: Avoid name shadowing with exported stopTracks().

The parameter stopTracks shadows the exported function stopTracks, which is confusing. Rename the boolean and reuse the helper.

-	return useCallback(
-		(stopTracks = true) => {
+	return useCallback(
+		(shouldStopTracks = true) => {
@@
-				if (stopTracks) {
-					stream.getTracks().forEach((track) => {
-						track.stop();
-					});
-				}
+				if (shouldStopTracks) {
+					stopTracks(stream);
+				}

Also applies to: 95-99


70-89: Reduce enumerateDevices calls to once per stream.

You enumerate on every track; enumerate once and map by id.

-				stream.getTracks().forEach((track) => {
-					const { deviceId } = track.getSettings();
-					if (!deviceId) {
-						return;
-					}
-
-					if (track.kind === 'audio' && navigator.mediaDevices.enumerateDevices) {
-						navigator.mediaDevices.enumerateDevices().then((devices) => {
-							const device = devices.find((device) => device.deviceId === deviceId);
-							if (!device) {
-								return;
-							}
-							setInputMediaDevice({
-								id: device.deviceId,
-								label: device.label,
-								type: 'audioinput',
-							});
-						});
-					}
-				});
+				const audioTracks = stream.getAudioTracks();
+				if (audioTracks.length && navigator.mediaDevices.enumerateDevices) {
+					navigator.mediaDevices.enumerateDevices().then((devices) => {
+						const byId = new Map(devices.map((d) => [d.deviceId, d]));
+						for (const t of audioTracks) {
+							const id = t.getSettings().deviceId;
+							const dev = id && byId.get(id);
+							if (dev) {
+								setInputMediaDevice({ id: dev.deviceId, label: dev.label, type: 'audioinput' });
+								break;
+							}
+						}
+					});
+				}

157-179: Same optimization in the new flow.

Mirror the single enumerateDevices() call here too.

-					stream.getTracks().forEach((track) => {
-						const { deviceId } = track.getSettings();
-						if (!deviceId) {
-							return;
-						}
-
-						if (track.kind === 'audio' && navigator.mediaDevices.enumerateDevices) {
-							navigator.mediaDevices.enumerateDevices().then((devices) => {
-								const device = devices.find((device) => device.deviceId === deviceId);
-								if (!device) {
-									return;
-								}
-								setInputMediaDevice({
-									id: device.deviceId,
-									label: device.label,
-									type: 'audioinput',
-								});
-							});
-						}
-					});
+					const audioTracks = stream.getAudioTracks();
+					if (audioTracks.length && navigator.mediaDevices.enumerateDevices) {
+						navigator.mediaDevices.enumerateDevices().then((devices) => {
+							const byId = new Map(devices.map((d) => [d.deviceId, d]));
+							for (const t of audioTracks) {
+								const id = t.getSettings().deviceId;
+								const dev = id && byId.get(id);
+								if (dev) {
+									setInputMediaDevice({ id: dev.deviceId, label: dev.label, type: 'audioinput' });
+									break;
+								}
+							}
+						});
+					}

181-191: Add fallback for Overconstrained/NotFound errors when a preselected device is unavailable.

If deviceId: { exact } is stale, getUserMedia rejects. Retry with { audio: true } to recover.

-				if (state === 'granted') {
-					requestDevice({
-						onAccept: resolve,
-						onReject: reject,
-						constraints,
-					});
-					return;
-				}
+				const attemptRequest = (cs: MediaStreamConstraints) =>
+					requestDevice({
+						onAccept: resolve,
+						onReject: (err) => {
+							if (err && (err.name === 'OverconstrainedError' || err.name === 'NotFoundError') && cs !== { audio: true }) {
+								// fallback to generic audio
+								requestDevice({
+									onAccept: resolve,
+									onReject: reject,
+									constraints: { audio: true },
+								});
+							} else {
+								reject(err);
+							}
+						},
+						constraints: cs,
+					});
+
+				if (state === 'granted') {
+					attemptRequest(constraints);
+					return;
+				}
@@
-					requestDevice?.({
-						onReject: reject,
-						onAccept: (...args) => {
+					requestDevice?.({
+						onReject: (err) => {
+							if (err && (err.name === 'OverconstrainedError' || err.name === 'NotFoundError')) {
+								attemptRequest({ audio: true });
+								return;
+							}
+							reject(err);
+						},
+						onAccept: (...args) => {
 							onAccept(...args);
 							setModal(null);
 						},
 						constraints,
 					});

Also applies to: 194-203


205-208: Reject with a typed, actionable error on cancel.

Helps callers distinguish user cancel vs. permission/system errors.

-				const onCancel = () => {
-					reject();
+				const onCancel = () => {
+					reject(new DOMException('Permission prompt canceled by user', 'AbortError'));
 					setModal(null);
 				};
packages/ui-voip/src/v2/components/DevicePicker.tsx (3)

53-61: Normalize click behavior with output items (stop bubbling to menu)

Output rows preventDefault/stopPropagation; input rows don’t. For consistency and to avoid GenericMenu reacting to row clicks differently by section, mirror the handler on input items.

Apply:

   return {
     id: `${device.id}-input`,
     content: (
       <Box is='span' title={device.label} fontSize={14}>
         {device.label}
       </Box>
     ),
     addon: <RadioButton onChange={() => onDeviceChange(device)} checked={device.id === selectedAudioDevices?.audioInput?.id} />,
+    onClick(e?: MouseEvent<HTMLElement>) {
+      e?.preventDefault();
+      e?.stopPropagation();
+    },
   };

101-116: Handle permission rejection to avoid stale “opening” attempts

If the permission prompt is denied, we never update local state. Add a catch to keep the control closed decisively.

Apply:

 void requestPermission({
   actionType: 'device-change',
 }).then((stream) => {
   stopTracks(stream);
   setIsOpen(true);
-});
+}).catch(() => {
+  setIsOpen(false);
+});

118-128: Localize the button’s accessible label/tooltip

Button currently hardcodes label='customize'. Provide a localized title/aria-label so SR/tooltip text matches the menu title.

Apply:

-      button={<DevicePickerButton secondary={secondary} tiny={!secondary} />}
+      button={
+        <DevicePickerButton
+          secondary={secondary}
+          tiny={!secondary}
+          title={t('Device_settings_lowercase')}
+          aria-label={t('Device_settings_lowercase')}
+        />
+      }
packages/ui-voip/src/v2/components/PeerInfo/PeerInfo.stories.tsx (1)

11-29: Stories updated to new prop names — looks good

The story reflects callerId/displayName. Consider adding args to make knobs usable, but not required.

packages/ui-voip/src/v2/views/IncomingCall.tsx (2)

31-33: Pass handler directly; avoid extra closure

No need to wrap onAccept in an arrow.

Apply:

- <Button medium name='phone' icon='phone' success flexGrow={1} onClick={() => onAccept()}>
+ <Button medium name='phone' icon='phone' success flexGrow={1} onClick={onAccept}>

10-15: Guard against double-accept taps (optional UX hardening)

If onAccept does async work, consider disabling the button while pending to prevent race conditions or duplicate accept calls.

Would you like a small state wrapper here to gate repeated clicks?

Also applies to: 27-33

apps/meteor/client/ui.ts (2)

18-18: Consider feature-gating the new room action.

If voice calls should be configurable/permissioned, wrap useMediaCallRoomAction behind a setting/license/permission check to avoid surfacing the action when disabled.

Also applies to: 74-76


37-37: Remove commented code before merge.

Drop the commented import of useVoiceCallRoomAction to keep the module clean.

-// import { useVoiceCallRoomAction } from './hooks/roomActions/useVoiceCallRoomAction';
apps/meteor/client/providers/MeteorProvider.tsx (1)

52-53: Clean up leftover comments.

Remove the commented MediaCallsProvider markers.

-{/* <MediaCallsProvider> */}
 <MediaCallProvider>
   <OmnichannelCallProvider>
     <OmnichannelProvider>{children}</OmnichannelProvider>
   </OmnichannelCallProvider>
 </MediaCallProvider>
-{/* </MediaCallsProvider> */}

Also applies to: 57-58

apps/meteor/client/sidebar/header/hooks/useUserMenu.tsx (1)

5-5: Gate the “Voice call” menu item behind capability/permission.

If calls are disabled or unavailable for the user, hide this section to avoid a dead action.

Would you like this gated by a setting (e.g., useSetting('VoIP_Enabled')) or a permission check? I can wire it accordingly.

Also applies to: 18-19, 32-37, 48-50

packages/ui-voip/src/v2/views/OngoingCall.tsx (3)

48-48: Localize the “Dialpad” label.

Use t('Dialpad') to match other localized labels.

-<ActionButton icon='dialpad' label='Dialpad' {...keypadButtonProps} />
+<ActionButton icon='dialpad' label={t('Dialpad')} {...keypadButtonProps} />

53-57: Safer display name for hangup label (fallback).

Guard against missing displayName/number and reuse the same display-name logic used elsewhere.

-<ActionButton
-  label={t('Voice_call__user__hangup', { user: 'userId' in peerInfo ? peerInfo.displayName : peerInfo.number })}
+<ActionButton
+  label={t('Voice_call__user__hangup', {
+    user: 'userId' in peerInfo ? (peerInfo.displayName || peerInfo.userId) : (peerInfo.number || t('Unknown')),
+  })}
   icon='phone-off'
   danger
   onClick={onEndCall}
/>

50-51: Toggle icons for Hold are identical.

Both entries are 'pause-shape-unfilled'; consider a resume icon (e.g., 'play') for the unheld state.

Do you want 'play' for resume? I can update it.

apps/meteor/client/hooks/roomActions/useMediaCallRoomAction.ts (2)

53-65: Gate the action until peer info is ready to avoid opening the generic dialer.

If the query hasn’t resolved yet, callee is undefined and the action opens the dialer instead of calling the peer.

Apply:

-  if (!peerId) {
+  if (!peerId || !peerInfo) {
     return undefined;
   }
@@
-  }, [peerId, title, icon, action]);
+  }, [peerId, peerInfo, title, icon, action]);

34-34: Avoid broad type assertion; use non-null assertion with enabled guard.

Cleaner and keeps types honest.

-const { data } = useUserInfoQuery({ userId: peerId as string }, { enabled: !!peerId });
+const { data } = useUserInfoQuery({ userId: peerId! }, { enabled: Boolean(peerId) });
packages/ui-voip/src/v2/components/PeerInfo/InternalUser.tsx (1)

11-12: A11y: label avatar/fallback and align center.

Add an accessible label for the avatar/fallback and vertically center content.

-  <Box display='flex' flexDirection='row'>
-    <Box mie={8}>{avatarUrl ? <Avatar url={avatarUrl} size='x20' /> : <Icon name='user' size='x20' />}</Box>
+  <Box display='flex' flexDirection='row' alignItems='center'>
+    <Box mie={8}>
+      {avatarUrl ? <Avatar url={avatarUrl} size='x20' title={displayName} /> : <Icon name='user' size='x20' aria-hidden='true' />}
+    </Box>
packages/ui-voip/src/v2/TransferModal.tsx (3)

20-26: Wire isLoading to UI.

Prop exists but is unused; disable/indicate loading on confirm.

-type TransferModalProps = {
+type TransferModalProps = {
   isLoading?: boolean;
   onCancel(): void;
   onConfirm(kind: 'user' | 'sip', id: string): void;
 };
 
-const TransferModal = ({ onCancel, onConfirm }: TransferModalProps) => {
+const TransferModal = ({ onCancel, onConfirm, isLoading = false }: TransferModalProps) => {

50-51: Don’t throw in event handler.

Throwing here can crash the UI. Log and no-op instead.

-  throw new Error('Peer info is missing userId and/or number');
+  console.error('Peer info is missing userId and/or number');
+  return;

73-75: Disable confirm while loading or no peer.

Prevents invalid submissions and double-clicks.

-  <Button danger onClick={confirm} disabled={!peer}>
+  <Button danger onClick={confirm} disabled={!peer || isLoading} loading={isLoading}>
packages/ui-voip/src/v2/useMediaCallAction.ts (1)

13-15: Fallback when displayName is empty.

Ensure we show the number if name is blank.

-    const getDisplayName = (peerInfo: { displayName?: string; number?: string }) => {
-      return 'displayName' in peerInfo ? peerInfo?.displayName : peerInfo?.number;
-    };
+    const getDisplayName = (peerInfo: { displayName?: string; number?: string }) => {
+      const name = peerInfo.displayName?.trim();
+      return name && name.length > 0 ? name : peerInfo.number;
+    };
packages/ui-voip/src/v2/MockedMediaCallProvider.tsx (3)

90-99: Accept optional callee in onToggleWidget.

Keeps the mock aligned with the hook’s usage and sets the selected peer when provided.

-const onToggleWidget = () => {
+const onToggleWidget = (callee?: PeerInfo) => {
+  if (callee) {
+    setPeerInfo(callee);
+  }
   switch (widgetState) {
     case 'closed':
       setWidgetState('new');
       break;
     case 'new':
       setWidgetState('closed');
       break;
   }
 };

29-31: Tighten type of device param.

Avoid any in mocks where easy. Use unknown to retain type-safety.

-const onDeviceChange = (device: any) => {
+const onDeviceChange = (device: unknown) => {

7-7: Consider typing mock data.

Replace any[] with a small interface for better DX in stories/tests.

-const myData: any[] = Array.from({ length: 100 }, (_, i) => ({ value: `user-${i}`, label: `User ${i}`, identifier: `000${i}`, avatarUrl }));
+type MockOption = { value: string; label: string; identifier: string; avatarUrl: string };
+const myData: MockOption[] = Array.from({ length: 100 }, (_, i) => ({ value: `user-${i}`, label: `User ${i}`, identifier: `000${i}`, avatarUrl }));
packages/ui-voip/src/v2/views/NewCall.tsx (1)

41-43: Disable Call until a peer is selected.

Prevents starting a call without target.

-  <Button medium icon='phone' success flexGrow={1} onClick={onCall}>
+  <Button medium icon='phone' success flexGrow={1} onClick={onCall} disabled={!peerInfo}>
packages/ui-voip/src/providers/MediaCallProvider.tsx (3)

83-93: Handle permission rejection on accept.

Same issue on accept path; add try/catch.

-const onAccept = async () => {
+const onAccept = async () => {
   console.log('onAccept');
   if (session.state !== 'ringing') {
     console.error('Cannot accept call in state', session.state);
     return;
   }
-
-  const stream = await requestDevice({ actionType: 'incoming' });
-  session.acceptCall(stream.getTracks()[0]);
+  try {
+    const stream = await requestDevice({ actionType: 'incoming' });
+    await session.acceptCall(stream.getTracks()[0]);
+  } catch (err) {
+    console.error('Accept canceled/failed', err);
+  }
 };

174-191: Autocomplete: avoid sending undefined in exceptions and guard items.

exceptions: [user?.username] may include undefined, and items may be absent. Build selector conditionally and coerce items safely.

-const getAutocompleteOptions = async (filter: string) => {
-  const { items } = await usersAutoCompleteEndpoint({
-    selector: JSON.stringify({ term: filter, exceptions: [user?.username] }),
-  });
-  return (
-    items.map((user) => {
+const getAutocompleteOptions = async (filter: string) => {
+  const selector =
+    user?.username ? { term: filter, exceptions: [user.username] } : { term: filter };
+  const data = await usersAutoCompleteEndpoint({ selector: JSON.stringify(selector) });
+  const items = Array.isArray((data as any)?.items) ? (data as any).items : [];
+  return (
+    items.map((user: any) => {
       const label = user.name || user.username;
       // TODO: This endpoint does not provide the extension number, which is necessary to show in the UI.
       const identifier = user.username !== label ? user.username : undefined;
       return {
         value: user._id,
         label,
         identifier,
         avatarUrl: getAvatarPath({ username: user.username, etag: user.avatarETag }),
       };
     }) || []
   );
 };

41-51: Tiny: pass the callback directly to endedCall listener.

No need to wrap callback in a new function.

- useCallSounds(
-   session.state,
-   useCallback(
-     (callback) => {
-       if (!instance) {
-         return;
-       }
-       return instance.on('endedCall', () => callback());
-     },
-     [instance],
-   ),
- );
+ useCallSounds(
+   session.state,
+   useCallback((cb) => (instance ? instance.on('endedCall', cb) : undefined), [instance]),
+ );
packages/ui-voip/src/providers/useMediaSession.ts (5)

105-147: Don’t set an undefined state on instance updates.

When deriveWidgetStateFromCallState returns undefined (e.g., transient ‘hangup’), the reducer may receive { state: undefined } and corrupt the session. Guard before dispatch.

     const updateSessionState = () => {
       const mainCall = instance.getMainCall();
       if (!mainCall) {
         dispatch({ type: 'reset' });
         return;
       }
       const { contact, state: callState, role, muted, held } = mainCall;
       const state = deriveWidgetStateFromCallState(callState, role);
+      if (!state) {
+        return;
+      }

173-182: Dispatch the new mute/hold values explicitly.

mainCall.setMuted(!mainCall.muted) then reading mainCall.muted can race with internal updates. Compute the next value first.

-const toggleMute = () => {
+const toggleMute = () => {
   const mainCall = instance?.getMainCall();
   if (!mainCall) {
     return;
   }
-
-  mainCall.setMuted(!mainCall.muted);
-  dispatch({ type: 'mute', payload: { muted: mainCall.muted } });
+  const next = !mainCall.muted;
+  mainCall.setMuted(next);
+  dispatch({ type: 'mute', payload: { muted: next } });
 };

184-193: Same for hold.

-const toggleHold = () => {
+const toggleHold = () => {
   const mainCall = instance?.getMainCall();
   if (!mainCall) {
     return;
   }
-
-  mainCall.setHeld(!mainCall.held);
-  dispatch({ type: 'hold', payload: { held: mainCall.held } });
+  const next = !mainCall.held;
+  mainCall.setHeld(next);
+  dispatch({ type: 'hold', payload: { held: next } });
 };

137-145: PeerInfo completeness: add displayName fallback.

contact.displayName may be absent; fall back to username to satisfy the required displayName.

-const peerInfo = {
-  displayName: contact.displayName,
+const peerInfo = {
+  displayName: contact.displayName || contact.username || 'Unknown',
   userId: contact.id,
   username: contact.username,
   avatarUrl,
   callerId: contact.sipExtension,
 } as PeerInfo;

8-14: Nit: startedAt default is created at module load.

If used as a timestamp, consider setting it only when a call actually starts, or keep it null until then.

packages/ui-voip/src/providers/useMediaSessionInstance.ts (2)

50-55: Factory-not-set guard can throw at runtime.

If a call starts before the effect sets the factory, this throws. It’s probably fine because starting a call requires user action, but consider a safer default or setting the factory eagerly during store initialization.


62-64: Nit: align return type of sendSignal with transport type.

MediaSignalTransport<T> returns void; returning a Promise is unnecessary. No behavior change.

-console.warn('Media Call - Tried to send signal, but no sendSignalFn was set');
-return Promise.resolve();
+console.warn('Media Call - Tried to send signal, but no sendSignalFn was set');
+return;
packages/ui-voip/src/v2/MediaCallContext.ts (2)

93-143: Autocomplete hook looks good; one tiny polish.

Avoid shadowing options inside queryFn for readability.

- queryFn: async () => {
-   const options = await getAutocompleteOptions(debouncedFilter);
+ queryFn: async () => {
+   const fetched = await getAutocompleteOptions(debouncedFilter);
    if (debouncedFilter.length > 0) {
-     return [getFirstOption(debouncedFilter), ...options];
+     return [getFirstOption(debouncedFilter), ...fetched];
    }
-   return options;
+   return fetched;
 },

44-47: Forwarding path is in place; ensure provider actually forwards.

Context exposes onSelectPeer and onForward, but the provider’s session.forwardCall() is still a stub. Track a follow‑up to implement wire‑up once backend is ready.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between dcfd74e and 9d9c04f.

⛔ Files ignored due to path filters (2)
  • packages/ui-voip/src/v2/__snapshots__/MediaCallWidget.spec.tsx.snap is excluded by !**/*.snap
  • yarn.lock is excluded by !**/yarn.lock, !**/*.lock
📒 Files selected for processing (35)
  • apps/meteor/client/NavBarV2/NavBarVoipGroup/NavBarItemVoipDialer.tsx (1 hunks)
  • apps/meteor/client/NavBarV2/NavBarVoipGroup/NavBarVoipGroup.tsx (1 hunks)
  • apps/meteor/client/hooks/roomActions/useMediaCallRoomAction.ts (1 hunks)
  • apps/meteor/client/providers/DeviceProvider/DeviceProvider.tsx (1 hunks)
  • apps/meteor/client/providers/MeteorProvider.tsx (2 hunks)
  • apps/meteor/client/sidebar/header/hooks/useUserMenu.tsx (3 hunks)
  • apps/meteor/client/ui.ts (3 hunks)
  • packages/i18n/src/locales/en.i18n.json (6 hunks)
  • packages/ui-contexts/src/hooks/useMediaDevicePermission.ts (1 hunks)
  • packages/ui-voip/package.json (1 hunks)
  • packages/ui-voip/src/hooks/useDevicePermissionPrompt.tsx (3 hunks)
  • packages/ui-voip/src/index.ts (1 hunks)
  • packages/ui-voip/src/providers/MediaCallProvider.tsx (1 hunks)
  • packages/ui-voip/src/providers/useCallSounds.ts (1 hunks)
  • packages/ui-voip/src/providers/useMediaSession.ts (1 hunks)
  • packages/ui-voip/src/providers/useMediaSessionInstance.ts (1 hunks)
  • packages/ui-voip/src/providers/useMediaStream.ts (1 hunks)
  • packages/ui-voip/src/v2/MediaCallContext.ts (5 hunks)
  • packages/ui-voip/src/v2/MediaCallWidget.stories.tsx (1 hunks)
  • packages/ui-voip/src/v2/MockedMediaCallProvider.tsx (5 hunks)
  • packages/ui-voip/src/v2/TransferModal.tsx (1 hunks)
  • packages/ui-voip/src/v2/components/ActionButton.tsx (3 hunks)
  • packages/ui-voip/src/v2/components/DevicePicker.tsx (3 hunks)
  • packages/ui-voip/src/v2/components/PeerInfo/InternalUser.tsx (1 hunks)
  • packages/ui-voip/src/v2/components/PeerInfo/PeerInfo.stories.tsx (1 hunks)
  • packages/ui-voip/src/v2/components/PeerInfo/PeerInfo.tsx (1 hunks)
  • packages/ui-voip/src/v2/components/Widget/Widget.tsx (2 hunks)
  • packages/ui-voip/src/v2/components/Widget/WidgetDraggableContext.ts (1 hunks)
  • packages/ui-voip/src/v2/components/Widget/WidgetHandle.tsx (3 hunks)
  • packages/ui-voip/src/v2/index.ts (1 hunks)
  • packages/ui-voip/src/v2/useKeypad.tsx (2 hunks)
  • packages/ui-voip/src/v2/useMediaCallAction.ts (1 hunks)
  • packages/ui-voip/src/v2/views/IncomingCall.tsx (2 hunks)
  • packages/ui-voip/src/v2/views/NewCall.tsx (2 hunks)
  • packages/ui-voip/src/v2/views/OngoingCall.tsx (3 hunks)
🧰 Additional context used
🧬 Code graph analysis (21)
packages/ui-voip/src/providers/useMediaSessionInstance.ts (8)
packages/ui-voip/src/v2/MediaCallContext.ts (1)
  • State (22-22)
packages/media-signaling/src/definition/signals/Transport.ts (1)
  • MediaSignalTransport (1-1)
packages/media-signaling/src/definition/signals/client/MediaSignal.ts (1)
  • ClientMediaSignal (15-24)
packages/media-signaling/src/lib/Session.ts (1)
  • MediaSignalingSession (37-524)
packages/media-signaling/src/definition/services/webrtc/IWebRTCProcessor.ts (1)
  • WebRTCProcessorConfig (36-42)
packages/media-signaling/src/lib/services/webrtc/Processor.ts (1)
  • MediaCallWebRTCProcessor (9-419)
packages/ui-voip/src/hooks/useIceServers.ts (1)
  • useIceServers (7-16)
packages/media-signaling/src/definition/signals/server/MediaSignal.ts (1)
  • ServerMediaSignal (7-12)
packages/ui-voip/src/v2/components/DevicePicker.tsx (1)
packages/ui-voip/src/hooks/useDevicePermissionPrompt.tsx (2)
  • useDevicePermissionPrompt2 (132-214)
  • stopTracks (50-54)
packages/ui-voip/src/providers/useMediaSession.ts (6)
packages/ui-voip/src/providers/useMediaSessionInstance.ts (1)
  • SessionInfo (27-27)
packages/ui-voip/src/v2/MediaCallContext.ts (2)
  • PeerInfo (20-20)
  • State (22-22)
packages/media-signaling/src/definition/call/IClientMediaCall.ts (2)
  • CallState (21-27)
  • CallRole (17-17)
packages/media-signaling/src/lib/Session.ts (1)
  • MediaSignalingSession (37-524)
packages/ui-contexts/src/index.ts (1)
  • useUserAvatarPath (81-81)
packages/media-signaling/src/lib/Call.ts (1)
  • contact (77-79)
apps/meteor/client/NavBarV2/NavBarVoipGroup/NavBarItemVoipDialer.tsx (2)
packages/ui-voip/src/v2/index.ts (1)
  • useMediaCallAction (4-4)
packages/ui-voip/src/v2/useMediaCallAction.ts (1)
  • useMediaCallAction (7-55)
apps/meteor/client/sidebar/header/hooks/useUserMenu.tsx (2)
packages/core-typings/src/IUser.ts (1)
  • IUser (186-252)
packages/ui-voip/src/v2/useMediaCallAction.ts (1)
  • useMediaCallAction (7-55)
packages/ui-voip/src/providers/useCallSounds.ts (2)
packages/ui-voip/src/v2/MediaCallContext.ts (1)
  • State (22-22)
packages/ui-contexts/src/index.ts (1)
  • useCustomSound (31-31)
packages/ui-voip/src/v2/components/PeerInfo/InternalUser.tsx (1)
packages/ui-voip/src/v2/components/PeerInfo/PeerInfo.stories.tsx (1)
  • InternalUser (11-29)
packages/ui-voip/src/v2/useKeypad.tsx (1)
apps/meteor/app/utils/lib/i18n.ts (1)
  • t (6-6)
packages/ui-voip/src/v2/MediaCallWidget.stories.tsx (1)
packages/livechat/src/components/Button/index.tsx (1)
  • Button (34-99)
packages/ui-voip/src/v2/components/Widget/WidgetHandle.tsx (1)
packages/ui-voip/src/v2/components/Widget/WidgetDraggableContext.ts (1)
  • useDraggableWidget (13-19)
packages/ui-voip/src/providers/MediaCallProvider.tsx (5)
packages/ui-voip/src/providers/useMediaSessionInstance.ts (1)
  • useMediaSessionInstance (107-149)
packages/ui-voip/src/providers/useMediaSession.ts (1)
  • useMediaSession (94-276)
packages/ui-voip/src/hooks/useDevicePermissionPrompt.tsx (2)
  • useDevicePermissionPrompt2 (132-214)
  • stopTracks (50-54)
packages/ui-voip/src/providers/useCallSounds.ts (1)
  • useCallSounds (6-21)
packages/ui-voip/src/v2/MediaCallContext.ts (1)
  • PeerInfo (20-20)
apps/meteor/client/ui.ts (1)
apps/meteor/client/hooks/roomActions/useMediaCallRoomAction.ts (1)
  • useMediaCallRoomAction (26-66)
packages/ui-voip/src/providers/useMediaStream.ts (1)
packages/ui-client/src/hooks/useSafeRefCallback/useSafeRefCallback.ts (1)
  • useSafeRefCallback (29-44)
packages/ui-voip/src/v2/components/Widget/Widget.tsx (2)
packages/ui-voip/src/components/VoipPopupDraggable/DraggableCore.ts (1)
  • useDraggable (525-584)
packages/ui-voip/src/v2/components/Widget/WidgetDraggableContext.ts (1)
  • DragContext (3-11)
apps/meteor/client/hooks/roomActions/useMediaCallRoomAction.ts (4)
packages/ui-contexts/src/index.ts (3)
  • useUserId (82-82)
  • useUserAvatarPath (81-81)
  • TranslationKey (103-103)
apps/meteor/client/hooks/useUserInfoQuery.ts (1)
  • useUserInfoQuery (11-18)
packages/ui-voip/src/v2/useMediaCallAction.ts (1)
  • useMediaCallAction (7-55)
packages/ui-voip/src/v2/MediaCallContext.ts (1)
  • PeerInfo (20-20)
packages/ui-voip/src/v2/TransferModal.tsx (1)
packages/ui-voip/src/v2/MediaCallContext.ts (2)
  • usePeerAutocomplete (93-143)
  • PeerInfo (20-20)
packages/ui-voip/src/v2/views/OngoingCall.tsx (2)
packages/ui-voip/src/v2/MediaCallContext.ts (1)
  • useMediaCallContext (79-81)
packages/ui-voip/src/v2/useKeypad.tsx (1)
  • useKeypad (15-38)
packages/ui-voip/src/v2/views/NewCall.tsx (1)
packages/ui-voip/src/v2/MediaCallContext.ts (2)
  • useMediaCallContext (79-81)
  • usePeerAutocomplete (93-143)
packages/ui-voip/src/v2/MockedMediaCallProvider.tsx (2)
packages/ui-voip/src/v2/index.ts (1)
  • PeerInfo (2-2)
packages/ui-voip/src/v2/MediaCallContext.ts (1)
  • PeerInfo (20-20)
packages/ui-voip/src/v2/views/IncomingCall.tsx (2)
packages/ui-voip/src/v2/index.ts (1)
  • useMediaCallContext (1-1)
packages/ui-voip/src/v2/MediaCallContext.ts (1)
  • useMediaCallContext (79-81)
packages/ui-voip/src/hooks/useDevicePermissionPrompt.tsx (1)
packages/ui-contexts/src/hooks/useMediaDevicePermission.ts (2)
  • useMediaDeviceMicrophonePermission (33-60)
  • requestDevice (5-18)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: CodeQL-Build
  • GitHub Check: CodeQL-Build
🔇 Additional comments (18)
packages/ui-voip/src/v2/components/Widget/WidgetHandle.tsx (1)

15-31: Ref-forwarding removed — no callers pass a ref

Repo-wide search found WidgetHandle only used in:
packages/ui-voip/src/v2/components/Widget/Widget.stories.tsx and views NewCall.tsx, IncomingCall.tsx, OutgoingCall.tsx, OngoingCall.tsx — no <WidgetHandle … ref=…> occurrences and no forwardRef(WidgetHandle) were found.

packages/ui-voip/src/v2/MediaCallWidget.stories.tsx (1)

43-43: Good fix: prevent React event from being passed to onToggleWidget

Wrapping onToggleWidget avoids leaking the click event into an optional parameter. Consistent with the updated API.

packages/ui-voip/src/v2/useKeypad.tsx (2)

3-3: i18n hook import looks right

Importing useTranslation locally is appropriate for storybook/isolated usage.


9-13: Resolved: useKeypad buttonProps API — callers updated.
Only consumer found: packages/ui-voip/src/v2/views/OngoingCall.tsx (destructures and spreads returned buttonProps into ActionButton); no other usages found.

packages/ui-voip/package.json (1)

23-23: Keep @rocket.chat/media-signaling in dependencies — runtime imports are used

Runtime imports found in:

  • packages/ui-voip/src/providers/useMediaSessionInstance.ts — MediaSignalingSession, MediaCallWebRTCProcessor
  • packages/ui-voip/src/providers/useMediaStream.ts — MediaSignalingSession
  • packages/ui-voip/src/providers/useMediaSession.ts — MediaSignalingSession, CallState, CallRole
apps/meteor/client/NavBarV2/NavBarVoipGroup/NavBarVoipGroup.tsx (1)

11-16: Unconditional render — confirm gating lives elsewhere

apps/meteor/client/NavBarV2/NavBarVoipGroup/NavBarVoipGroup.tsx has the showVoip/isCallEnabled checks commented out (lines ~11–16) so the VoIP group always renders. Repo search returned no MediaCallProvider/useMediaCallAction matches. Verify that MediaCallProvider or an equivalent hook now enforces availability/permissions for non‑VoIP users; if not, restore local gating here or hide/disable NavBarItemVoipDialer to avoid broken UI.

packages/ui-voip/src/v2/components/ActionButton.tsx (1)

12-30: LGTM: forwardRef correctly wired to IconButton.

Ref forwarding and prop shaping look good.

packages/ui-voip/src/hooks/useDevicePermissionPrompt.tsx (1)

132-214: Migrate legacy hook usages: replace useDevicePermissionPrompt with useDevicePermissionPrompt2

Both hooks are currently used — migrate callers, adjust for API differences, update tests/exports, then remove the legacy hook (TODO).

  • Legacy-hook callers (must migrate):

    • packages/ui-voip/src/components/VoipSettingsButton/VoipSettingsButton.tsx (import at line 9, usage at 23)
    • packages/ui-voip/src/components/VoipPopup/views/VoipDialerView.tsx (import at line 6, usage at 24)
    • packages/ui-voip/src/components/VoipActions/VoipActions.tsx (import at line 4, usage at 41)
    • apps/meteor/client/hooks/roomActions/useVoiceCallRoomAction.tsx (import at line 3, usage at ~48)
    • packages/ui-voip/src/hooks/useDevicePermissionPrompt.spec.tsx (tests)
    • packages/ui-voip/src/hooks/index.ts (re-exports legacy hook)
  • New-hook callers:

    • packages/ui-voip/src/v2/components/DevicePicker.tsx (import at line 11, usage at 99)
    • packages/ui-voip/src/providers/MediaCallProvider.tsx (import at line 18, usage at 38)

Action items:

  • Verify and update callers/tests for the API difference: legacy hook is callback-based (onAccept/onReject), useDevicePermissionPrompt2 is async and returns Promise (accepts constraints).
  • Update packages/ui-voip/src/hooks/index.ts to stop exporting the legacy hook once callers are migrated.
  • Remove the legacy hook and its TODO only after all callers and tests are updated.
packages/i18n/src/locales/en.i18n.json (4)

1664-1664: Confirm _lowercase key naming — single occurrence found

Only one key uses this suffix: packages/i18n/src/locales/en.i18n.json:1664 — "Device_settings_lowercase": "Device settings". Confirm intent: either document this aliasing convention for translators or remove/rename the _lowercase suffix to match canonical keys for consistency.


1039-1039: Alias added for dialpad — add to other locales or confirm

"Close_dialpad" is present only in packages/i18n/src/locales/en.i18n.json:1039 — add the same key to non-EN locale files or confirm this alias is intentionally EN-only to avoid fallback/missing translations.


3581-3581: Add translations for "New_voice_call" and confirm usage

Found usage: packages/ui-voip/src/v2/useMediaCallAction.ts — title: t('New_voice_call').
Key exists only in packages/i18n/src/locales/en.i18n.json (line 3580).
Add the key to other locale files (or ensure fallback/translation pipeline covers it).


5670-5673: Per-user voice-call labels — placeholder verified; base key is used

  • Confirmed: JSON uses "{{user}}" and callers pass a user property (packages/ui-voip/src/v2/useMediaCallAction.ts — t('Voice_call__user__hangup'|'...cancel'|'...reject', { user: getDisplayName(peerInfo) }); packages/ui-voip/src/v2/views/OngoingCall.tsx — t('Voice_call__user__hangup', { user: 'userId' in peerInfo ? peerInfo.displayName : peerInfo.number })).
  • Confirmed: base key Voice_call__user_ is referenced (packages/ui-voip/src/v2/useMediaCallAction.ts:43 — t('Voice_call__user_', { user: getDisplayName(callee) })); do not remove.
packages/ui-voip/src/v2/components/PeerInfo/PeerInfo.tsx (1)

8-12: Narrowing by 'displayName' looks correct; confirm prop is required on InternalUser

Runtime guard via 'displayName' in props is fine as long as displayName is required on InternalUser props; if optional, union narrowing can misroute. Please confirm it’s required.

apps/meteor/client/NavBarV2/NavBarVoipGroup/NavBarItemVoipDialer.tsx (1)

2-2: Hook migration looks good; keep the inline wrapper.

Using an arrow for onClick avoids passing the click event into action(callee?), which would be a type/logic footgun if passed directly.

Also applies to: 10-12

packages/ui-voip/src/v2/index.ts (1)

1-4: Barrel exports are clear and consistent.

Public surface (context, type, widget, action hook) looks correct.

apps/meteor/client/providers/MeteorProvider.tsx (1)

2-2: Provider swap and ordering look correct.

MediaCallProvider is nested under DeviceProvider and above consumers; expected for context availability.

Confirm that MediaCallProvider does not require any additional top-level providers (e.g., translation/sounds) beyond what’s already here.

Also applies to: 52-59

packages/ui-voip/src/v2/useMediaCallAction.ts (1)

45-46: onToggleWidget accepts an optional PeerInfo — no changes needed.

MediaCallContext declares onToggleWidget: (peerInfo?: PeerInfo) => void and MediaCallProvider implements const onToggleWidget = (peerInfo?: PeerInfo) => { ... }; passing callee from useMediaCallAction is valid (mock/default no-arg implementations are compatible).

apps/meteor/client/hooks/roomActions/useMediaCallRoomAction.ts (1)

59-59: Don't cast a translated string to TranslationKey — make the action title a string or pass an i18n key+params.

useMediaCallRoomAction returns a translated string; casting it to TranslationKey is unsound and can cause double-translation.
Offender: apps/meteor/client/hooks/roomActions/useMediaCallRoomAction.ts:59 — title: title as TranslationKey
Type: apps/meteor/client/views/room/contexts/RoomToolboxContext.ts:13 — title: TranslationKey

  • Fix A (preferred): change RoomToolboxActionConfig.title from TranslationKey → string and update callers.
  • Fix B: keep TranslationKey and have the hook return a translation key (+ params) instead of a translated string.

Copy link
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: 2

♻️ Duplicate comments (2)
packages/ui-voip/src/providers/useMediaSession.ts (1)

33-38: Renegotiating mapping added — good catch.

Thanks for handling 'renegotiating' as 'ongoing'.

packages/ui-voip/src/providers/useMediaSessionInstance.ts (1)

99-105: Dispose previous MediaSignalingSession when userId changes (leak fix).

Old instances keep timers/listeners alive. Clean them before creating a new one.

   public getInstance(userId?: string) {
     if (!userId) {
       return null;
     }
 
     if (this.sessionInstance?.userId === userId) {
       return this.sessionInstance;
     }
 
-    // TODO: maybe we need a cleanup step on the instance?
+    // Cleanup previous instance to avoid leaks.
+    if (this.sessionInstance) {
+      try {
+        (this.sessionInstance as any).disableStateReport?.();
+        (this.sessionInstance as any).destroy?.();
+        (this.sessionInstance as any).dispose?.();
+      } catch {}
+    }
     return this.makeInstance(userId);
   }
🧹 Nitpick comments (7)
packages/ui-voip/src/providers/useMediaSession.ts (5)

33-43: Add a default branch to avoid undefined widget state.

Future call states would currently return undefined.

 const deriveWidgetStateFromCallState = (callState: CallState, callRole: CallRole): State | undefined => {
   switch (callState) {
     case 'active':
     case 'accepted':
     case 'renegotiating':
       return 'ongoing';
     case 'none':
     case 'ringing':
       return callRole === 'callee' ? 'ringing' : 'calling';
+    default:
+      // Be defensive against new/unknown states.
+      return 'ongoing';
   }
 };

122-141: Harden PeerInfo construction (missing fields) and avoid broad type assertion.

contact fields are optional; default defensively to prevent undefined surfacing in UI.

-  const peerInfo = {
-    displayName: contact.displayName,
-    userId: contact.id,
-    username: contact.username,
-    avatarUrl,
-    callerId: contact.sipExtension,
-  } as PeerInfo; // TODO: Some of these fields are typed as optional, but I think they are always present.
+  const displayName = contact.displayName ?? contact.username ?? contact.id ?? '';
+  const peerInfo: PeerInfo = {
+    displayName,
+    userId: contact.id ?? undefined,
+    username: contact.username ?? undefined,
+    avatarUrl,
+    callerId: contact.sipExtension,
+  };

200-210: Await call.accept and handle rejection.

Prevents unhandled rejections and keeps flow consistent with async signature.

-    const acceptCall = async () => {
+    const acceptCall = async () => {
       if (!instance) {
         return;
       }
 
       const call = instance.getMainCall();
       if (!call || call.state !== 'ringing') {
         return;
       }
-      call.accept();
+      try {
+        await call.accept();
+      } catch (error) {
+        console.error('Error accepting call', error);
+      }
     };

224-231: Await setDeviceId to honor the async API.

Ensures device switch sequencing and error surfacing.

-    const changeDevice = async (deviceId: string) => {
+    const changeDevice = async (deviceId: string) => {
       if (!instance) {
         return;
       }
 
-      instance.setDeviceId(deviceId);
+      await instance.setDeviceId(deviceId);
     };

8-14: Consider deferring startedAt until a call starts.

Starting the timer at hook init/reset may skew “call duration” UX.

packages/ui-voip/src/providers/useMediaSessionInstance.ts (2)

68-74: SSR-safe access to sessionStorage.

Referencing window in SSR throws; guard it.

-  private get oldSessionId() {
-    if (!window.sessionStorage) {
-      return undefined;
-    }
-    return window.sessionStorage.getItem(SESSION_ID_KEY) ?? undefined;
-  }
+  private get oldSessionId() {
+    if (typeof window === 'undefined' || !('sessionStorage' in window) || !window.sessionStorage) {
+      return undefined;
+    }
+    return window.sessionStorage.getItem(SESSION_ID_KEY) ?? undefined;
+  }

84-85: Use a stronger randomStringFactory.

Timestamps can collide; prefer crypto-based IDs.

-      randomStringFactory: () => new Date().toISOString(),
+      randomStringFactory: () => {
+        try {
+          if (typeof crypto !== 'undefined' && 'randomUUID' in crypto) {
+            return crypto.randomUUID();
+          }
+          if (typeof crypto !== 'undefined' && 'getRandomValues' in crypto) {
+            const arr = new Uint8Array(16);
+            crypto.getRandomValues(arr);
+            return Array.from(arr, (b) => b.toString(16).padStart(2, '0')).join('');
+          }
+        } catch {}
+        return `${Date.now().toString(36)}-${Math.random().toString(36).slice(2)}`;
+      },
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 9d9c04f and fb6b474.

📒 Files selected for processing (4)
  • packages/ui-voip/src/providers/MediaCallProvider.tsx (1 hunks)
  • packages/ui-voip/src/providers/useMediaSession.ts (1 hunks)
  • packages/ui-voip/src/providers/useMediaSessionInstance.ts (1 hunks)
  • packages/ui-voip/src/providers/useMediaStream.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • packages/ui-voip/src/providers/useMediaStream.ts
  • packages/ui-voip/src/providers/MediaCallProvider.tsx
🧰 Additional context used
🧬 Code graph analysis (2)
packages/ui-voip/src/providers/useMediaSession.ts (5)
packages/ui-voip/src/providers/useMediaSessionInstance.ts (1)
  • SessionInfo (27-27)
packages/ui-voip/src/v2/MediaCallContext.ts (2)
  • PeerInfo (20-20)
  • State (22-22)
packages/media-signaling/src/definition/call/IClientMediaCall.ts (2)
  • CallState (21-27)
  • CallRole (17-17)
packages/media-signaling/src/lib/Session.ts (1)
  • MediaSignalingSession (37-524)
packages/media-signaling/src/lib/Call.ts (1)
  • contact (77-79)
packages/ui-voip/src/providers/useMediaSessionInstance.ts (7)
packages/ui-voip/src/v2/MediaCallContext.ts (1)
  • State (22-22)
packages/media-signaling/src/definition/signals/Transport.ts (1)
  • MediaSignalTransport (1-1)
packages/media-signaling/src/definition/signals/client/MediaSignal.ts (1)
  • ClientMediaSignal (15-24)
packages/media-signaling/src/definition/services/webrtc/IWebRTCProcessor.ts (1)
  • WebRTCProcessorConfig (36-42)
packages/media-signaling/src/lib/services/webrtc/Processor.ts (1)
  • MediaCallWebRTCProcessor (9-419)
packages/ui-voip/src/hooks/useIceServers.ts (1)
  • useIceServers (7-16)
packages/media-signaling/src/definition/signals/server/MediaSignal.ts (1)
  • ServerMediaSignal (7-12)

@pierre-lehnen-rc pierre-lehnen-rc added this to the 7.11.0 milestone Sep 22, 2025
@gabriellsh gabriellsh changed the base branch from feat/media-calls-backend to feat/media-calls-sip September 22, 2025 17:31
@gabriellsh gabriellsh marked this pull request as ready for review September 22, 2025 23:42
@gabriellsh gabriellsh requested a review from a team as a code owner September 22, 2025 23:42
Copy link
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: 3

🧹 Nitpick comments (5)
apps/meteor/client/hooks/roomActions/useMediaCallRoomAction.ts (3)

59-63: Don’t cast a localized string to TranslationKey.

useMediaCallAction returns a translated string, not a key. Casting to TranslationKey can cause double-translation or missing-key lookups. Prefer passing a plain string (or use a content/label prop if supported) instead of forcing the type.

Please confirm RoomToolboxActionConfig['title'] accepts a string. If it strictly requires a key, consider extending the type to also accept strings (common in dynamic-title cases) or introducing a label prop.


34-34: Avoid unsafe cast on query params.

Use a non-null assertion with the enabled guard to better express intent and avoid type‑unsound as string.

Apply:

- const { data } = useUserInfoQuery({ userId: peerId as string }, { enabled: !!peerId });
+ const { data } = useUserInfoQuery({ userId: peerId! }, { enabled: !!peerId });

41-47: Fallback displayName shouldn’t be empty.

If neither name nor username exists, fallback to a stable identifier to avoid blank UI.

Apply:

-      displayName: data.user.name || data.user.username || '',
+      displayName: data.user.name || data.user.username || data.user._id,
apps/meteor/client/sidebar/header/hooks/useUserMenu.tsx (1)

50-54: Section without a title—confirm UX.

Rendering a nameless section with a single item is fine if consistent with menu UX. Alternatively, merge into “Status” or give it a small heading (e.g., “Calls”).

apps/meteor/client/views/room/hooks/useUserInfoActions/actions/useUserMediaCallAction.ts (1)

34-35: Optional: include avatar ETag for cache busting.

If available on the user object, pass etag to getAvatarUrl to avoid stale avatars.

- const avatarUrl = user.username ? getAvatarUrl({ username: user.username }) : getAvatarUrl({ userId: user._id });
+ const avatarUrl = user.username
+   ? getAvatarUrl({ username: user.username, etag: (user as any).avatarETag })
+   : getAvatarUrl({ userId: user._id, etag: (user as any).avatarETag });
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Jira integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 73783d8 and c671a4e.

📒 Files selected for processing (10)
  • .changeset/sweet-ghosts-teach.md (1 hunks)
  • apps/meteor/client/NavBarV2/NavBarVoipGroup/NavBarItemVoipDialer.tsx (0 hunks)
  • apps/meteor/client/NavBarV2/NavBarVoipGroup/NavBarVoipGroup.tsx (1 hunks)
  • apps/meteor/client/hooks/roomActions/useMediaCallRoomAction.ts (1 hunks)
  • apps/meteor/client/providers/MediaCallProvider.tsx (1 hunks)
  • apps/meteor/client/providers/MeteorProvider.tsx (2 hunks)
  • apps/meteor/client/sidebar/header/hooks/useUserMenu.tsx (3 hunks)
  • apps/meteor/client/views/room/UserCard/UserCardWithData.tsx (1 hunks)
  • apps/meteor/client/views/room/hooks/useUserInfoActions/actions/useUserMediaCallAction.ts (1 hunks)
  • apps/meteor/client/views/room/hooks/useUserInfoActions/useUserInfoActions.ts (5 hunks)
💤 Files with no reviewable changes (1)
  • apps/meteor/client/NavBarV2/NavBarVoipGroup/NavBarItemVoipDialer.tsx
✅ Files skipped from review due to trivial changes (1)
  • .changeset/sweet-ghosts-teach.md
🚧 Files skipped from review as they are similar to previous changes (2)
  • apps/meteor/client/providers/MeteorProvider.tsx
  • apps/meteor/client/NavBarV2/NavBarVoipGroup/NavBarVoipGroup.tsx
🧰 Additional context used
🧬 Code graph analysis (6)
apps/meteor/client/views/room/UserCard/UserCardWithData.tsx (1)
apps/meteor/client/views/room/hooks/useUserInfoActions/useUserInfoActions.ts (1)
  • UserInfoAction (46-46)
apps/meteor/client/hooks/roomActions/useMediaCallRoomAction.ts (4)
packages/ui-contexts/src/index.ts (3)
  • useUserId (82-82)
  • useUserAvatarPath (81-81)
  • TranslationKey (103-103)
apps/meteor/client/hooks/useUserInfoQuery.ts (1)
  • useUserInfoQuery (11-18)
packages/ui-voip/src/v2/useMediaCallAction.ts (1)
  • useMediaCallAction (7-61)
packages/ui-voip/src/v2/MediaCallContext.ts (1)
  • PeerInfo (22-22)
apps/meteor/client/sidebar/header/hooks/useUserMenu.tsx (1)
packages/ui-voip/src/v2/useMediaCallAction.ts (1)
  • useMediaCallAction (7-61)
apps/meteor/client/views/room/hooks/useUserInfoActions/useUserInfoActions.ts (2)
packages/ui-contexts/src/index.ts (1)
  • useLayoutHiddenActions (43-43)
apps/meteor/client/views/room/hooks/useUserInfoActions/actions/useUserMediaCallAction.ts (1)
  • useUserMediaCallAction (9-50)
apps/meteor/client/providers/MediaCallProvider.tsx (2)
packages/ui-contexts/src/index.ts (1)
  • usePermission (55-55)
packages/ui-voip/src/index.ts (1)
  • MediaCallContext (8-8)
apps/meteor/client/views/room/hooks/useUserInfoActions/actions/useUserMediaCallAction.ts (5)
packages/core-typings/src/IUser.ts (1)
  • IUser (186-253)
packages/core-typings/src/IRoom.ts (1)
  • IRoom (21-95)
apps/meteor/client/views/room/hooks/useUserInfoActions/useUserInfoActions.ts (1)
  • UserInfoAction (46-46)
packages/ui-contexts/src/index.ts (2)
  • useUserId (82-82)
  • useUserAvatarPath (81-81)
packages/ui-voip/src/v2/MediaCallContext.ts (1)
  • useMediaCallContext (112-121)
🔇 Additional comments (8)
apps/meteor/client/providers/MediaCallProvider.tsx (2)

15-33: Gating flow and fallbacks look solid.

Early returns for unlicensed/unauthorized states cleanly prevent wiring the base provider and keep children rendered. Good separation of concerns.

If UX requires a warning for unauthorized (not just unlicensed), confirm whether onToggleWidget should also dispatch a permission-specific message rather than be disabled.


10-14: Verification blocked — confirm permission keys, license key, and MediaCallContext shape

File: apps/meteor/client/providers/MediaCallProvider.tsx (lines 10–14, 17–21, 27–29)

  • Permissions: confirm 'allow-internal-voice-calls' and 'allow-external-voice-calls' are defined in the permissions registry and granted where used; import canonical permission constants instead of string literals when available.
  • License module: confirm the canonical identifier across the codebase is 'teams-voip' (search/replace if inconsistent).
  • MediaCallContext shape: confirm the context/type accepts state: 'unlicensed' | 'unauthorized' and exposes the handlers/props used here with correct names/types (onToggleWidget, onEndCall, peerInfo).

If any identifiers/types differ, update this file to use the canonical constants/types and re-run verification.

apps/meteor/client/sidebar/header/hooks/useUserMenu.tsx (1)

32-39: LGTM: media call item is correctly derived from hook state.

Item uses the returned icon/title and binds action properly; will gracefully disappear when unauthorized.

apps/meteor/client/views/room/hooks/useUserInfoActions/useUserInfoActions.ts (3)

26-34: Good addition: optional disabled on actions.

Type extension is minimal and backward‑compatible.


86-91: Correct replacement of legacy VoIP action.

userMediaCall is cleanly integrated and gated by availability.


121-149: Resolved — GenericMenuItemProps includes disabled.

GenericMenuItemProps declares disabled?: boolean in packages/ui-client/src/components/GenericMenu/GenericMenuItem.tsx, so disabled states will be preserved when spreading actions.

apps/meteor/client/views/room/UserCard/UserCardWithData.tsx (1)

93-95: Unable to verify: confirm UserCardAction accepts disabled and prevents onClick when disabled.
Repository search returned no matches for UserCardAction; verify that apps/meteor/client/components/UserCard's UserCardAction declares a disabled prop and short-circuits/ignores onClick when disabled is true — paste the component if you want me to re-check.

apps/meteor/client/views/room/hooks/useUserInfoActions/actions/useUserMediaCallAction.ts (1)

3-3: No change required — importing from the package root already gives the non‑throwing external context.

packages/ui-voip/src/index.ts re-exports useMediaCallExternalContext as useMediaCallContext (line 8), so importing { useMediaCallContext } from '@rocket.chat/ui-voip' yields the safe external variant; leave as-is.

Likely an incorrect or invalid review comment.

@gabriellsh gabriellsh marked this pull request as draft September 22, 2025 23:54
@gabriellsh
Copy link
Member Author

Moved back to draft since it's not yet pointing to develop and will most likely require a rebase. (Shouldn't have come out of draft to start with)

Base automatically changed from feat/media-calls-sip to develop September 23, 2025 04:25
@ggazzo ggazzo dismissed pierre-lehnen-rc’s stale review September 23, 2025 04:25

The base branch was changed.

Copy link
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

🧹 Nitpick comments (3)
apps/meteor/client/hooks/roomActions/useMediaCallRoomAction.ts (3)

33-36: Gate users.info query when DM is blocked; avoid unnecessary fetch.

Skip the query if either no peerId or the conversation is blocked. Also move the blocked computation above the query to use it in enabled.

 	const peerId = getPeerId(uids, ownUserId);
 
-	const { data } = useUserInfoQuery({ userId: peerId as string }, { enabled: !!peerId });
+	const blocked = subscription?.blocked || subscription?.blocker;
+
+	const { data } = useUserInfoQuery({ userId: peerId as string }, { enabled: !!peerId && !blocked });
 
-	const callAction = useMediaCallAction(peerInfo);
-
-	const blocked = subscription?.blocked || subscription?.blocker;
+	const callAction = useMediaCallAction(peerInfo);

Also applies to: 53-54


18-21: Nit: fix comment grammar and clarity.

-	// If no id, it's an one user DM. If more than one, it's a group dm. Both are not supported as of now.
+	// If none, it's a one‑user DM; if more than one, it's a group DM. Both are not supported for calls (yet).

63-65: Title is already translated; casting to TranslationKey may be misleading.

callAction.title is a translated string. Confirm RoomToolboxActionConfig.title accepts a string; otherwise consider widening it to string | TranslationKey (or pass a key and translate at render time).

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Jira integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between aeb4a5d and 8a3e042.

📒 Files selected for processing (1)
  • apps/meteor/client/hooks/roomActions/useMediaCallRoomAction.ts (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
apps/meteor/client/hooks/roomActions/useMediaCallRoomAction.ts (4)
packages/ui-contexts/src/index.ts (3)
  • useUserId (82-82)
  • useUserAvatarPath (81-81)
  • TranslationKey (103-103)
apps/meteor/client/hooks/useUserInfoQuery.ts (1)
  • useUserInfoQuery (11-18)
packages/ui-voip/src/v2/MediaCallContext.ts (1)
  • PeerInfo (22-22)
packages/ui-voip/src/v2/useMediaCallAction.ts (1)
  • useMediaCallAction (7-61)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (7)
  • GitHub Check: 🔎 Code Check / TypeScript
  • GitHub Check: 🔨 Test Unit / Unit Tests
  • GitHub Check: 🔨 Test Storybook / Test Storybook
  • GitHub Check: 🔎 Code Check / Code Lint
  • GitHub Check: 📦 Meteor Build - coverage
  • GitHub Check: CodeQL-Build
  • GitHub Check: CodeQL-Build
🔇 Additional comments (1)
apps/meteor/client/hooks/roomActions/useMediaCallRoomAction.ts (1)

37-51: Good memoization and PeerInfo mapping.

Using useMemo for peerInfo and passing it to useMediaCallAction addresses earlier feedback.

@gabriellsh gabriellsh added the stat: QA assured Means it has been tested and approved by a company insider label Sep 24, 2025
@dionisio-bot dionisio-bot bot added the stat: ready to merge PR tested and approved waiting for merge label Sep 24, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

stat: QA assured Means it has been tested and approved by a company insider stat: ready to merge PR tested and approved waiting for merge

Projects

None yet

Development

Successfully merging this pull request may close these issues.

7 participants