-
Notifications
You must be signed in to change notification settings - Fork 13k
feat: Media Call Client #36717
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: Media Call Client #36717
Conversation
|
Looks like this PR is ready to merge! 🎉 |
🦋 Changeset detectedLatest commit: f657c2d The changes in this PR will be included in the next version bump. This PR includes changesets to release 41 packages
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 |
Codecov Report❌ Patch coverage is Additional details and impacted files@@ 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
Flags with carried forward coverage won't be shown. Click here to find out more. 🚀 New features to boost your workflow:
|
d31db5c to
6256fd8
Compare
WalkthroughReplaces 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
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
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)
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60–90 minutes Possibly related PRs
Suggested reviewers
Poem
Pre-merge checks and finishing touches❌ Failed checks (2 inconclusive)
✅ Passed checks (3 passed)
✨ Finishing touches
🧪 Generate unit tests
📜 Recent review detailsConfiguration used: CodeRabbit UI Review profile: CHILL Plan: Pro Disabled knowledge base sources:
📒 Files selected for processing (2)
🚧 Files skipped from review as they are similar to previous changes (2)
⏰ 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)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
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 readersThe 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: CallboundingRef(null)on unmount and guard for SSRThe 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 consumersProvider 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:childrenis redundant inWidgetProps
ComponentProps<typeof WidgetBase>already includeschildren; 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 nitOptional: expose
isOpenin 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 mergeDead commented imports accumulate noise. If feature gating is gone, delete; if temporary, add TODO with tracking issue.
21-23: OK to drop toggler if replacedIf 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 mobileOptional hardening: prefix with
voidto signal intentional ignore;playsInlinehelps 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 elementIf 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).
requestDevicecan 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
stopTracksshadows the exported functionstopTracks, 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,getUserMediarejects. 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” attemptsIf 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/tooltipButton 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 goodThe story reflects
callerId/displayName. Consider addingargsto make knobs usable, but not required.packages/ui-voip/src/v2/views/IncomingCall.tsx (2)
31-33: Pass handler directly; avoid extra closureNo need to wrap
onAcceptin 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
onAcceptdoes 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,
calleeis 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
anyin mocks where easy. Useunknownto 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 includeundefined, anditemsmay be absent. Buildselectorconditionally 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
callbackin 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
deriveWidgetStateFromCallStatereturnsundefined(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 readingmainCall.mutedcan 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.displayNamemay be absent; fall back tousernameto satisfy the requireddisplayName.-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
nulluntil 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>returnsvoid; 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
optionsinsidequeryFnfor 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
onSelectPeerandonForward, but the provider’ssession.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
⛔ Files ignored due to path filters (2)
packages/ui-voip/src/v2/__snapshots__/MediaCallWidget.spec.tsx.snapis excluded by!**/*.snapyarn.lockis 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 refRepo-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 onToggleWidgetWrapping
onToggleWidgetavoids 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 rightImporting
useTranslationlocally 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 usedRuntime 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 elsewhereapps/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 useDevicePermissionPrompt2Both 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_lowercasekey naming — single occurrence foundOnly 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
_lowercasesuffix 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 usageFound 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 InternalUserRuntime guard via
'displayName' in propsis fine as long asdisplayNameis required onInternalUserprops; 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) => voidand MediaCallProvider implementsconst onToggleWidget = (peerInfo?: PeerInfo) => { ... }; passingcalleefrom 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.
packages/ui-voip/src/v2/components/Widget/WidgetDraggableContext.ts
Outdated
Show resolved
Hide resolved
There was a problem hiding this 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
📒 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)
db2f6f5 to
a41ef27
Compare
There was a problem hiding this 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.
useMediaCallActionreturns a translated string, not a key. Casting toTranslationKeycan cause double-translation or missing-key lookups. Prefer passing a plain string (or use acontent/labelprop 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 alabelprop.
34-34: Avoid unsafe cast on query params.Use a non-null assertion with the
enabledguard to better express intent and avoid type‑unsoundas 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
namenorusernameexists, 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
etagtogetAvatarUrlto 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.
📒 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
onToggleWidgetshould also dispatch a permission-specific message rather than be disabled.
10-14: Verification blocked — confirm permission keys, license key, and MediaCallContext shapeFile: 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.
userMediaCallis 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 acceptsdisabledand preventsonClickwhen disabled.
Repository search returned no matches for UserCardAction; verify that apps/meteor/client/components/UserCard's UserCardAction declares adisabledprop and short-circuits/ignoresonClickwhendisabledis 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.
apps/meteor/client/views/room/hooks/useUserInfoActions/actions/useUserMediaCallAction.ts
Outdated
Show resolved
Hide resolved
apps/meteor/client/views/room/hooks/useUserInfoActions/actions/useUserMediaCallAction.ts
Show resolved
Hide resolved
|
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) |
The base branch was changed.
There was a problem hiding this 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
blockedcomputation above the query to use it inenabled.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.titleis a translated string. ConfirmRoomToolboxActionConfig.titleaccepts a string; otherwise consider widening it tostring | 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.
📒 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
useMemoforpeerInfoand passing it touseMediaCallActionaddresses earlier feedback.
Proposed changes (including videos or screenshots)
Issue(s)
VAI-74
VAI-111
Steps to test or reproduce
Further comments
Summary by CodeRabbit
New Features
Improvements
Accessibility
Localization
Chores