From 0c841e22a8c17e0f542b13c9c39b217e15c4ac9b Mon Sep 17 00:00:00 2001 From: Aleksander Nicacio da Silva Date: Mon, 11 May 2026 16:19:06 -0300 Subject: [PATCH 1/2] feat: added displayName and avatarUrl support for external calls --- .../src/sip/providers/IncomingSipCall.ts | 41 +++---- .../src/components/PeerInfo/ExternalUser.tsx | 18 ++++ .../src/components/PeerInfo/InternalUser.tsx | 2 +- .../components/PeerInfo/PeerInfo.stories.tsx | 27 ++++- .../src/components/PeerInfo/PeerInfo.tsx | 15 +-- .../__snapshots__/PeerInfo.spec.tsx.snap | 101 ++++++++++++++++++ .../ui-voip/src/components/PeerInfo/index.ts | 1 + packages/ui-voip/src/context/definitions.d.ts | 4 + .../usePeekMediaSessionPeerInfo.spec.tsx | 6 +- .../src/context/usePeerAutocomplete.spec.tsx | 15 +-- .../src/context/usePeerAutocomplete.ts | 3 +- .../src/hooks/useMediaCallAction.spec.tsx | 10 +- .../ui-voip/src/hooks/useMediaCallAction.ts | 2 +- .../src/providers/MockedMediaCallProvider.tsx | 2 + .../ui-voip/src/providers/useMediaSession.ts | 35 +++--- .../derivePeerInfoFromInstanceContact.spec.ts | 20 ++++ .../derivePeerInfoFromInstanceContact.ts | 3 + .../derivePeerInfoFromInstanceState.spec.ts | 3 + .../utils/derivePeerInfoFromInstanceState.ts | 1 + .../src/views/MediaCallWidget/OngoingCall.tsx | 6 +- .../MediaCallWidget/OngoingCallWithScreen.tsx | 6 +- 21 files changed, 260 insertions(+), 61 deletions(-) create mode 100644 packages/ui-voip/src/components/PeerInfo/ExternalUser.tsx diff --git a/ee/packages/media-calls/src/sip/providers/IncomingSipCall.ts b/ee/packages/media-calls/src/sip/providers/IncomingSipCall.ts index 762e66ee5185e..0240ab331a3ef 100644 --- a/ee/packages/media-calls/src/sip/providers/IncomingSipCall.ts +++ b/ee/packages/media-calls/src/sip/providers/IncomingSipCall.ts @@ -439,39 +439,44 @@ export class IncomingSipCall extends BaseSipCall { throw new SipError(SipErrorCodes.NOT_FOUND); } - private static async getRocketChatCallerFromInvite(req: SrfRequest): Promise { - logger.debug({ - msg: 'IncomingSipCall.getRocketChatCallerFromInvite', - callingNumber: req.callingNumber, - calledNumber: req.calledNumber, - }); + private static getDisplayNameFromInvite(req: SrfRequest): string | undefined { + const removeQuotes = (str?: string): string | undefined => str?.replace(/^"|"$/g, '').trim(); - if (req.callingNumber && typeof req.callingNumber === 'string') { - const userContact = await mediaCallDirector.cast.getContactForExtensionNumber(req.callingNumber, { preferredType: 'sip' }); - if (userContact) { - return userContact; + if (req.has('X-RocketChat-Caller-Name')) { + const headerValue = req.get('X-RocketChat-Caller-Name'); + if (headerValue) { + return headerValue; } } - return null; + if (req.has('p-asserted-identity')) { + const pAssertedIdentity = removeQuotes(req.getParsedHeader('p-asserted-identity')?.name); + if (pAssertedIdentity) { + return pAssertedIdentity; + } + } + + if (req.has('from')) { + const fromHeader = removeQuotes(req.getParsedHeader('from')?.name); + if (fromHeader) { + return fromHeader; + } + } + + return undefined; } private static async getCallerContactFromInvite(sessionId: string, req: SrfRequest): Promise> { logger.debug({ msg: 'IncomingSipCall.getCallerContactFromInvite' }); - const callerBase = await this.getRocketChatCallerFromInvite(req); - const displayNameFromHeader = req.has('X-RocketChat-Caller-Name') && req.get('X-RocketChat-Caller-Name'); + const displayName = this.getDisplayNameFromInvite(req); const usernameFromHeader = req.has('X-RocketChat-Caller-Username') && req.get('X-RocketChat-Caller-Username'); - - const displayName = displayNameFromHeader || callerBase?.displayName || req.from; - const username = usernameFromHeader || callerBase?.username || req.callingNumber; - const sipExtension = req.callingNumber; const defaultContactInfo: MediaCallContactInformation = { - username, sipExtension, displayName: displayName || sipExtension, + ...(usernameFromHeader && { username: usernameFromHeader }), }; const contact = await mediaCallDirector.cast.getContactForExtensionNumber(sipExtension, { requiredType: 'sip' }, defaultContactInfo); diff --git a/packages/ui-voip/src/components/PeerInfo/ExternalUser.tsx b/packages/ui-voip/src/components/PeerInfo/ExternalUser.tsx new file mode 100644 index 0000000000000..e9a6a64e07aac --- /dev/null +++ b/packages/ui-voip/src/components/PeerInfo/ExternalUser.tsx @@ -0,0 +1,18 @@ +import InternalUser from './InternalUser'; +import PhoneNumber from './PhoneNumber'; + +export type ExternalUserProps = { + number: string; + displayName?: string; + avatarUrl?: string; +}; + +const ExternalUser = ({ number, displayName, avatarUrl }: ExternalUserProps) => { + if (displayName) { + return ; + } + + return ; +}; + +export default ExternalUser; diff --git a/packages/ui-voip/src/components/PeerInfo/InternalUser.tsx b/packages/ui-voip/src/components/PeerInfo/InternalUser.tsx index 96cfa6c812ea6..81ffab95415c5 100644 --- a/packages/ui-voip/src/components/PeerInfo/InternalUser.tsx +++ b/packages/ui-voip/src/components/PeerInfo/InternalUser.tsx @@ -3,7 +3,7 @@ import { Avatar, Box, Icon, StatusBullet } from '@rocket.chat/fuselage'; import type { Slot } from './useInfoSlots'; -type InternalUserProps = { +export type InternalUserProps = { displayName: string; status?: UserStatus; avatarUrl?: string; diff --git a/packages/ui-voip/src/components/PeerInfo/PeerInfo.stories.tsx b/packages/ui-voip/src/components/PeerInfo/PeerInfo.stories.tsx index d5398d933ab9d..21f963da62ec8 100644 --- a/packages/ui-voip/src/components/PeerInfo/PeerInfo.stories.tsx +++ b/packages/ui-voip/src/components/PeerInfo/PeerInfo.stories.tsx @@ -62,5 +62,30 @@ export const InternalUserWithRemoteStatus: StoryFn = () => { }; export const ExternalUser: StoryFn = () => { - return ; + return ; +}; + +export const ExternalUserWithDisplayName: StoryFn = () => { + return ; +}; + +export const ExternalUserWithDisplayNameAndAvatar: StoryFn = () => { + return ( + + ); }; diff --git a/packages/ui-voip/src/components/PeerInfo/PeerInfo.tsx b/packages/ui-voip/src/components/PeerInfo/PeerInfo.tsx index b95ec5ca11f8c..d5bd7e4c05774 100644 --- a/packages/ui-voip/src/components/PeerInfo/PeerInfo.tsx +++ b/packages/ui-voip/src/components/PeerInfo/PeerInfo.tsx @@ -1,14 +1,15 @@ -import type { ComponentProps } from 'react'; +import { ExternalUser, InternalUser } from '.'; +import type { ExternalUserProps } from './ExternalUser'; +import type { InternalUserProps } from './InternalUser'; -import { InternalUser, PhoneNumber } from '.'; - -export type PeerInfoProps = ComponentProps | ComponentProps; +export type PeerInfoProps = (InternalUserProps & { external?: false }) | (ExternalUserProps & { external: true }); const PeerInfo = (props: PeerInfoProps) => { - if ('displayName' in props) { - return ; + if (props.external) { + return ; } - return ; + + return ; }; export default PeerInfo; diff --git a/packages/ui-voip/src/components/PeerInfo/__snapshots__/PeerInfo.spec.tsx.snap b/packages/ui-voip/src/components/PeerInfo/__snapshots__/PeerInfo.spec.tsx.snap index 2cfd2cab63995..83eabfd9abe5e 100644 --- a/packages/ui-voip/src/components/PeerInfo/__snapshots__/PeerInfo.spec.tsx.snap +++ b/packages/ui-voip/src/components/PeerInfo/__snapshots__/PeerInfo.spec.tsx.snap @@ -23,6 +23,107 @@ exports[`renders ExternalUser without crashing 1`] = ` `; +exports[`renders ExternalUserWithDisplayName without crashing 1`] = ` + +
+
+
+ +
+
+
+ +
+ Jane Doe +
+
+
+
+ 1234567890 +
+
+
+
+ +`; + +exports[`renders ExternalUserWithDisplayNameAndAvatar without crashing 1`] = ` + +
+
+
+
+ +
+
+
+
+ +
+ Jane Doe +
+
+
+
+ 1234567890 +
+
+
+
+ +`; + exports[`renders InternalUser without crashing 1`] = `
diff --git a/packages/ui-voip/src/components/PeerInfo/index.ts b/packages/ui-voip/src/components/PeerInfo/index.ts index 74b490b421713..a4000d2472687 100644 --- a/packages/ui-voip/src/components/PeerInfo/index.ts +++ b/packages/ui-voip/src/components/PeerInfo/index.ts @@ -1,4 +1,5 @@ export { default as InternalUser } from './InternalUser'; +export { default as ExternalUser } from './ExternalUser'; export { default as PhoneNumber } from './PhoneNumber'; export { default as PeerInfo } from './PeerInfo'; export { type PeerInfoProps } from './PeerInfo'; diff --git a/packages/ui-voip/src/context/definitions.d.ts b/packages/ui-voip/src/context/definitions.d.ts index 495bf5cf253fb..fb8ebd3a02024 100644 --- a/packages/ui-voip/src/context/definitions.d.ts +++ b/packages/ui-voip/src/context/definitions.d.ts @@ -2,6 +2,7 @@ import type { UserStatus } from '@rocket.chat/core-typings'; import type { CallFeature } from '@rocket.chat/media-signaling'; export type InternalPeerInfo = { + external: false; displayName: string; userId: string; username?: string; @@ -11,7 +12,10 @@ export type InternalPeerInfo = { }; export type ExternalPeerInfo = { + external: true; number: string; + displayName?: string; + avatarUrl?: string; }; export type ConnectionState = 'CONNECTED' | 'CONNECTING' | 'RECONNECTING'; diff --git a/packages/ui-voip/src/context/usePeekMediaSessionPeerInfo.spec.tsx b/packages/ui-voip/src/context/usePeekMediaSessionPeerInfo.spec.tsx index 464daafc926ae..19cba7b928a14 100644 --- a/packages/ui-voip/src/context/usePeekMediaSessionPeerInfo.spec.tsx +++ b/packages/ui-voip/src/context/usePeekMediaSessionPeerInfo.spec.tsx @@ -73,7 +73,7 @@ describe('usePeekMediaSessionPeerInfo', () => { wrapper: createWrapper(instance), }); - expect(result.current).toEqual({ number: '+5511999999999' }); + expect(result.current).toEqual({ external: true, number: '+5511999999999' }); }); it('returns internal peer info for user contact', () => { @@ -98,6 +98,7 @@ describe('usePeekMediaSessionPeerInfo', () => { }); expect(result.current).toEqual({ + external: false, displayName: 'John Doe', userId: 'userId123', username: 'johndoe', @@ -131,7 +132,7 @@ describe('usePeekMediaSessionPeerInfo', () => { wrapper: createWrapper(instance), }); - expect(result.current).toEqual({ number: '+5511999999999' }); + expect(result.current).toEqual({ external: true, number: '+5511999999999' }); act(() => { instanceState = null; @@ -155,6 +156,7 @@ describe('usePeekMediaSessionPeerInfo', () => { }); expect(result.current).toEqual({ + external: false, displayName: 'Jane Smith', userId: 'userId456', username: undefined, diff --git a/packages/ui-voip/src/context/usePeerAutocomplete.spec.tsx b/packages/ui-voip/src/context/usePeerAutocomplete.spec.tsx index 6153410802d72..0a0acf2803e02 100644 --- a/packages/ui-voip/src/context/usePeerAutocomplete.spec.tsx +++ b/packages/ui-voip/src/context/usePeerAutocomplete.spec.tsx @@ -119,7 +119,7 @@ describe('hook', () => { it('should return value when peerInfo has userId', () => { mockGetAutocompleteOptions.mockResolvedValue([]); - const peerInfo: PeerInfo = { userId: 'user1', displayName: 'User 1' }; + const peerInfo: PeerInfo = { external: false, userId: 'user1', displayName: 'User 1' }; const { result } = renderHook(() => usePeerAutocomplete(mockOnSelectPeer, peerInfo), { wrapper: appRoot(), @@ -130,7 +130,7 @@ describe('hook', () => { it('should return undefined value when peerInfo has no userId', () => { mockGetAutocompleteOptions.mockResolvedValue([]); - const peerInfo: PeerInfo = { number: '123456' }; + const peerInfo: PeerInfo = { external: true, number: '123456' }; const { result } = renderHook(() => usePeerAutocomplete(mockOnSelectPeer, peerInfo), { wrapper: appRoot(), @@ -165,7 +165,7 @@ describe('hook', () => { result.current.onChangeValue('rcx-first-option-123456'); }); - expect(mockOnSelectPeer).toHaveBeenCalledWith({ number: '123456' }); + expect(mockOnSelectPeer).toHaveBeenCalledWith({ external: true, number: '123456' }); }); it('should call onSelectPeer with full peer info when value matches option', async () => { @@ -187,6 +187,7 @@ describe('hook', () => { }); expect(mockOnSelectPeer).toHaveBeenCalledWith({ + external: false, userId: 'user1', displayName: 'User 1', avatarUrl: 'avatar.jpg', @@ -247,7 +248,7 @@ describe('hook', () => { const mockUseUserPresence = useUserPresence as jest.MockedFunction; mockGetAutocompleteOptions.mockResolvedValue([]); - const peerInfo: PeerInfo = { userId: 'user1', displayName: 'User 1', status: UserStatus.ONLINE }; + const peerInfo: PeerInfo = { external: false, userId: 'user1', displayName: 'User 1', status: UserStatus.ONLINE }; mockUseUserPresence.mockReturnValue({ _id: 'user1', status: UserStatus.AWAY, statusText: '' }); @@ -268,7 +269,7 @@ describe('hook', () => { const mockUseUserPresence = useUserPresence as jest.MockedFunction; mockGetAutocompleteOptions.mockResolvedValue([]); - const peerInfo: PeerInfo = { userId: 'user1', displayName: 'User 1', status: UserStatus.ONLINE }; + const peerInfo: PeerInfo = { external: false, userId: 'user1', displayName: 'User 1', status: UserStatus.ONLINE }; mockUseUserPresence.mockReturnValue({ _id: 'user1', status: UserStatus.ONLINE, statusText: '' }); @@ -302,7 +303,7 @@ describe('hook', () => { const mockUseUserPresence = useUserPresence as jest.MockedFunction; mockGetAutocompleteOptions.mockResolvedValue([]); - const peerInfo: PeerInfo = { number: '123456' }; + const peerInfo: PeerInfo = { external: true, number: '123456' }; mockUseUserPresence.mockReturnValue({ _id: 'user1', status: UserStatus.ONLINE, statusText: '' }); @@ -320,7 +321,7 @@ describe('hook', () => { const mockUseUserPresence = useUserPresence as jest.MockedFunction; mockGetAutocompleteOptions.mockResolvedValue([]); - const peerInfo: PeerInfo = { userId: 'user1', displayName: 'User 1', status: UserStatus.ONLINE }; + const peerInfo: PeerInfo = { external: false, userId: 'user1', displayName: 'User 1', status: UserStatus.ONLINE }; mockUseUserPresence.mockReturnValue(undefined); diff --git a/packages/ui-voip/src/context/usePeerAutocomplete.ts b/packages/ui-voip/src/context/usePeerAutocomplete.ts index fa7ab2c93f196..23039ce87c891 100644 --- a/packages/ui-voip/src/context/usePeerAutocomplete.ts +++ b/packages/ui-voip/src/context/usePeerAutocomplete.ts @@ -65,7 +65,7 @@ export const usePeerAutocomplete = (onSelectPeer: (peerInfo: PeerInfo) => void, } if (isFirstPeerAutocompleteOption(value)) { - onSelectPeer({ number: value.replace(PREFIX_FIRST_OPTION, '') }); + onSelectPeer({ external: true, number: value.replace(PREFIX_FIRST_OPTION, '') }); return; } @@ -76,6 +76,7 @@ export const usePeerAutocomplete = (onSelectPeer: (peerInfo: PeerInfo) => void, } onSelectPeer({ + external: false, userId: localInfo.value, displayName: localInfo.label, avatarUrl: localInfo.avatarUrl, diff --git a/packages/ui-voip/src/hooks/useMediaCallAction.spec.tsx b/packages/ui-voip/src/hooks/useMediaCallAction.spec.tsx index 1e51dd9d8caac..373c4099b3577 100644 --- a/packages/ui-voip/src/hooks/useMediaCallAction.spec.tsx +++ b/packages/ui-voip/src/hooks/useMediaCallAction.spec.tsx @@ -125,7 +125,7 @@ describe('useMediaCallAction', () => { }); it('uses number when peerInfo has no displayName (external peer)', () => { - usePeekMediaSessionPeerInfoMock.mockReturnValue({ number: '+5511999999999' }); + usePeekMediaSessionPeerInfoMock.mockReturnValue({ external: true, number: '+5511999999999' }); const { result } = renderHook(() => useMediaCallAction(), { wrapper: createWrapper(), @@ -166,7 +166,7 @@ describe('useMediaCallAction', () => { }); it('uses number when peerInfo has no displayName (external peer)', () => { - usePeekMediaSessionPeerInfoMock.mockReturnValue({ number: '+5511999999999' }); + usePeekMediaSessionPeerInfoMock.mockReturnValue({ external: true, number: '+5511999999999' }); const { result } = renderHook(() => useMediaCallAction(), { wrapper: createWrapper(), @@ -178,7 +178,7 @@ describe('useMediaCallAction', () => { describe('when callee is provided (available state)', () => { it('returns voice call action with callee displayName in title', () => { - const callee: PeerInfo = { displayName: 'Dave', userId: 'dave-id' }; + const callee: PeerInfo = { external: false, displayName: 'Dave', userId: 'dave-id' }; const { result } = renderHook(() => useMediaCallAction(callee), { wrapper: createWrapper(), @@ -192,7 +192,7 @@ describe('useMediaCallAction', () => { }); it('calls toggleWidget with callee when action is invoked', () => { - const callee: PeerInfo = { displayName: 'Dave', userId: 'dave-id' }; + const callee: PeerInfo = { external: false, displayName: 'Dave', userId: 'dave-id' }; const { result } = renderHook(() => useMediaCallAction(callee), { wrapper: createWrapper(), @@ -206,7 +206,7 @@ describe('useMediaCallAction', () => { }); it('uses number when callee has no displayName (external peer)', () => { - const callee: PeerInfo = { number: '+5511888888888' }; + const callee: PeerInfo = { external: true, number: '+5511888888888' }; const { result } = renderHook(() => useMediaCallAction(callee), { wrapper: createWrapper(), diff --git a/packages/ui-voip/src/hooks/useMediaCallAction.ts b/packages/ui-voip/src/hooks/useMediaCallAction.ts index 5d6cba3d72dc7..d5f0d0619b1d7 100644 --- a/packages/ui-voip/src/hooks/useMediaCallAction.ts +++ b/packages/ui-voip/src/hooks/useMediaCallAction.ts @@ -22,7 +22,7 @@ export const useMediaCallAction = ( } const getDisplayName = (peerInfo: { displayName?: string; number?: string }) => { - return 'displayName' in peerInfo ? peerInfo?.displayName : peerInfo?.number; + return peerInfo.displayName || peerInfo.number; }; if (state === 'ongoing' && peerInfo) { diff --git a/packages/ui-voip/src/providers/MockedMediaCallProvider.tsx b/packages/ui-voip/src/providers/MockedMediaCallProvider.tsx index ec13c9f75113b..e812f5352075e 100644 --- a/packages/ui-voip/src/providers/MockedMediaCallProvider.tsx +++ b/packages/ui-voip/src/providers/MockedMediaCallProvider.tsx @@ -33,6 +33,7 @@ const MockedMediaCallProvider = ({ held = false, }: MockedMediaCallProviderProps) => { const [peerInfo, setPeerInfo] = useState({ + external: false, displayName: 'John Doe', userId: '1234567890', avatarUrl, @@ -85,6 +86,7 @@ const MockedMediaCallProvider = ({ displayName: peerInfo.label, userId: peerInfo.value, avatarUrl: peerInfo.avatarUrl, + external: false as const, username: peerInfo.identifier, callerId: peerInfo.value, }); diff --git a/packages/ui-voip/src/providers/useMediaSession.ts b/packages/ui-voip/src/providers/useMediaSession.ts index 893bbd06ca5d0..8d51aef5674fe 100644 --- a/packages/ui-voip/src/providers/useMediaSession.ts +++ b/packages/ui-voip/src/providers/useMediaSession.ts @@ -142,6 +142,7 @@ export const useMediaSession = (instance?: MediaSignalingSession): MediaSessionS type: 'instance_updated', payload: { peerInfo: { + external: false, displayName: instanceState.title, userId: 'unknown', username: undefined, @@ -174,11 +175,26 @@ export const useMediaSession = (instance?: MediaSignalingSession): MediaSessionS const transferredBy = callTransferredBy?.displayName || callTransferredBy?.username || undefined; + const avatarUrl = (() => { + if (contact.username) { + return getAvatarUrl({ username: contact.username }); + } + + if (contact.type === 'user' && contact.id) { + return getAvatarUrl({ userId: contact.id }); + } + + return undefined; + })(); + if (contact.type === 'sip') { dispatch({ type: 'instance_updated', payload: { - peerInfo: derivePeerInfoFromInstanceContact(contact), + peerInfo: { + ...derivePeerInfoFromInstanceContact(contact), + avatarUrl, + }, transferredBy, state, muted, @@ -195,19 +211,10 @@ export const useMediaSession = (instance?: MediaSignalingSession): MediaSessionS return; } - const avatarUrl = (() => { - if (contact.username) { - return getAvatarUrl({ username: contact.username }); - } - - if (contact.id) { - return getAvatarUrl({ userId: contact.id }); - } - - return undefined; - })(); - - const peerInfo = { ...derivePeerInfoFromInstanceContact(contact), avatarUrl }; + const peerInfo = { + ...derivePeerInfoFromInstanceContact(contact), + avatarUrl, + }; dispatch({ type: 'instance_updated', diff --git a/packages/ui-voip/src/utils/derivePeerInfoFromInstanceContact.spec.ts b/packages/ui-voip/src/utils/derivePeerInfoFromInstanceContact.spec.ts index cc5da78707eb3..d3339bc9bb9c1 100644 --- a/packages/ui-voip/src/utils/derivePeerInfoFromInstanceContact.spec.ts +++ b/packages/ui-voip/src/utils/derivePeerInfoFromInstanceContact.spec.ts @@ -10,6 +10,7 @@ describe('derivePeerInfoFromInstanceContact', () => { id: '+5511999999999', }; expect(derivePeerInfoFromInstanceContact(contact)).toEqual({ + external: true, number: '+5511999999999', }); }); @@ -19,6 +20,7 @@ describe('derivePeerInfoFromInstanceContact', () => { type: 'sip', }; expect(derivePeerInfoFromInstanceContact(contact)).toEqual({ + external: true, number: 'unknown', }); }); @@ -29,9 +31,23 @@ describe('derivePeerInfoFromInstanceContact', () => { id: '', }; expect(derivePeerInfoFromInstanceContact(contact)).toEqual({ + external: true, number: 'unknown', }); }); + + it('returns external peer info with displayName when provided', () => { + const contact: CallContact = { + type: 'sip', + id: '+5511999999999', + displayName: 'Customer Support', + }; + expect(derivePeerInfoFromInstanceContact(contact)).toEqual({ + external: true, + number: '+5511999999999', + displayName: 'Customer Support', + }); + }); }); describe('user contact', () => { @@ -44,6 +60,7 @@ describe('derivePeerInfoFromInstanceContact', () => { sipExtension: '1001', }; expect(derivePeerInfoFromInstanceContact(contact)).toEqual({ + external: false, displayName: 'John Doe', userId: 'userId123', username: 'johndoe', @@ -56,6 +73,7 @@ describe('derivePeerInfoFromInstanceContact', () => { type: 'user', }; expect(derivePeerInfoFromInstanceContact(contact)).toEqual({ + external: false, displayName: 'unknown', userId: 'unknown', username: undefined, @@ -72,6 +90,7 @@ describe('derivePeerInfoFromInstanceContact', () => { sipExtension: '1002', }; expect(derivePeerInfoFromInstanceContact(contact)).toEqual({ + external: false, displayName: 'Jane Smith', userId: 'userId456', username: 'janesmith', @@ -86,6 +105,7 @@ describe('derivePeerInfoFromInstanceContact', () => { displayName: '', }; expect(derivePeerInfoFromInstanceContact(contact)).toEqual({ + external: false, displayName: 'unknown', userId: 'unknown', username: undefined, diff --git a/packages/ui-voip/src/utils/derivePeerInfoFromInstanceContact.ts b/packages/ui-voip/src/utils/derivePeerInfoFromInstanceContact.ts index 464ec76eb4572..5aac5a6d59ab0 100644 --- a/packages/ui-voip/src/utils/derivePeerInfoFromInstanceContact.ts +++ b/packages/ui-voip/src/utils/derivePeerInfoFromInstanceContact.ts @@ -8,7 +8,9 @@ const deriveExternalPeerInfoFromInstanceContact = (contact: CallContact): Extern } return { + external: true, number: contact.id || 'unknown', + ...(contact.displayName && { displayName: contact.displayName }), }; }; @@ -18,6 +20,7 @@ const deriveInternalPeerInfoFromInstanceContact = (contact: CallContact): Omit { state: 'ringing', }; expect(derivePeerInfoFromInstanceState(state)).toEqual({ + external: false, displayName: 'Someone', userId: 'unknown', username: undefined, @@ -68,6 +69,7 @@ describe('derivePeerInfoFromInstanceState', () => { state: 'active', }; expect(derivePeerInfoFromInstanceState(state)).toEqual({ + external: false, displayName: 'John Doe', userId: 'userId123', username: 'johndoe', @@ -103,6 +105,7 @@ describe('derivePeerInfoFromInstanceState', () => { state: 'active', }; expect(derivePeerInfoFromInstanceState(state)).toEqual({ + external: true, number: '+5511999999999', }); }); diff --git a/packages/ui-voip/src/utils/derivePeerInfoFromInstanceState.ts b/packages/ui-voip/src/utils/derivePeerInfoFromInstanceState.ts index 3ed05357bf452..276ba19f9e93e 100644 --- a/packages/ui-voip/src/utils/derivePeerInfoFromInstanceState.ts +++ b/packages/ui-voip/src/utils/derivePeerInfoFromInstanceState.ts @@ -5,6 +5,7 @@ import { derivePeerInfoFromInstanceContact } from './derivePeerInfoFromInstanceC export const derivePeerInfoFromInstanceState = (callState: AnyMediaCallData) => { if (!callState.confirmed) { return { + external: false as const, displayName: callState.title, userId: 'unknown', username: undefined, diff --git a/packages/ui-voip/src/views/MediaCallWidget/OngoingCall.tsx b/packages/ui-voip/src/views/MediaCallWidget/OngoingCall.tsx index 929f91d820a9e..b823434039feb 100644 --- a/packages/ui-voip/src/views/MediaCallWidget/OngoingCall.tsx +++ b/packages/ui-voip/src/views/MediaCallWidget/OngoingCall.tsx @@ -50,7 +50,7 @@ const OngoingCall = () => { - + {peerInfo.external ? : } @@ -74,7 +74,9 @@ const OngoingCall = () => { onClick={onForward} /> { - + {peerInfo.external ? : } {remoteScreen?.active && ( @@ -105,7 +105,9 @@ const OngoingCall = () => { /> Date: Thu, 14 May 2026 11:30:44 -0300 Subject: [PATCH 2/2] chore: changeset --- .changeset/fuzzy-terms-brake.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 .changeset/fuzzy-terms-brake.md diff --git a/.changeset/fuzzy-terms-brake.md b/.changeset/fuzzy-terms-brake.md new file mode 100644 index 0000000000000..7c07ead6665ce --- /dev/null +++ b/.changeset/fuzzy-terms-brake.md @@ -0,0 +1,12 @@ +--- +'@rocket.chat/media-calls': minor +'@rocket.chat/model-typings': minor +'@rocket.chat/ui-voip': minor +'@rocket.chat/models': minor +'@rocket.chat/i18n': minor +'@rocket.chat/core-typings': minor +'@rocket.chat/rest-typings': minor +'@rocket.chat/meteor': minor +--- + +Adds name and avatar resolution for external voice calls