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`] = `
+
+
+
+
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