Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 12 additions & 12 deletions react/features/base/participants/middleware.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
import { isForceMuted } from '../../av-moderation/functions';
import { UPDATE_BREAKOUT_ROOMS } from '../../breakout-rooms/actionTypes';
import { getBreakoutRooms } from '../../breakout-rooms/functions';
import { Participants } from '../../breakout-rooms/utils';
import { toggleE2EE } from '../../e2ee/actions';
import { MAX_MODE } from '../../e2ee/constants';
import { hideNotification, showNotification } from '../../notifications/actions';
Expand Down Expand Up @@ -367,20 +368,19 @@
const newRooms: any = {};

Object.entries(rooms).forEach(([ key, r ]) => {
const participants = r?.participants || {};
const jid = Object.keys(participants).find(p =>
p.slice(p.indexOf('/') + 1) === identifier);

if (jid) {
const participants = r?.participants || new Map();
const participant = Participants.findById(r, identifier);

if (participant) {
const updatedParticipants = new Map(participants);
updatedParticipants.set(participant.jid, {

Check failure on line 376 in react/features/base/participants/middleware.ts

View workflow job for this annotation

GitHub Actions / Lint

Expected blank line before this statement
...participant,
displayName: name
});

Check failure on line 380 in react/features/base/participants/middleware.ts

View workflow job for this annotation

GitHub Actions / Lint

Trailing spaces not allowed
newRooms[key] = {
...r,
participants: {
...participants,
[jid]: {
...participants[jid],
displayName: name
}
}
participants: updatedParticipants
};
} else {
newRooms[key] = r;
Expand Down
125 changes: 125 additions & 0 deletions react/features/breakout-rooms/MIGRATION_NOTES.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
# Breakout Rooms Participants Migration Notes

## Overview

This document describes the migration of `room.participants` from plain objects to `Map<string, IParticipant>` objects in the breakout rooms feature.

## Changes Made

### 1. Type Definitions

**File:** `react/features/breakout-rooms/types.ts`

- Added `IParticipant` interface
- Updated `IRoom` interface to use `Map<string, IParticipant>` instead of plain object

### 2. Utility Functions

**File:** `react/features/breakout-rooms/utils.ts`

Created a comprehensive utility module with helper functions:

- `Participants.keys(room)` - Get all participant keys
- `Participants.values(room)` - Get all participant values
- `Participants.entries(room)` - Get all participant entries
- `Participants.count(room)` - Get participant count
- `Participants.isEmpty(room)` - Check if room has participants
- `Participants.findByJid(room, jid)` - Find participant by exact JID
- `Participants.findByPartialJid(room, partialJid)` - Find participant by partial JID
- `Participants.findById(room, id)` - Find participant by ID
- `Participants.toJSON(room)` - Convert Map to JSON object
- `Participants.fromJSON(obj)` - Convert JSON object to Map

### 3. Code Changes

#### Functions (`functions.ts`)
- Replaced `Object.keys(breakoutRoomItem.participants).length` with `Participants.count(breakoutRoomItem)`
- Replaced `Object.keys(breakoutRoomItem.participants).map()` with `Participants.values(breakoutRoomItem).map()`

#### Middleware (`middleware.ts`)
- Replaced `Object.keys(participants).find()` with `Participants.findById()`
- Replaced `Object.keys(participants).find()` with `Participants.findByPartialJid()`
- Updated participant updates to use `Map.set()` instead of object spread

#### Actions (`actions.ts`)
- Replaced `Object.values(room.participants).forEach()` with `Participants.values(room).forEach()`
- Replaced `Object.keys(room.participants).length > 0` with `!Participants.isEmpty(room)`
- Replaced `participants[_participantId]` with `room.participants?.get(_participantId)`

#### UI Components
- **Web CollapsibleRoom:** Replaced `Object.keys(room?.participants || {}).length` with `Participants.count(room)`
- **Native CollapsibleRoom:** Replaced `Object.values(room.participants || {}).length` with `Participants.count(room)`
- **Context Menus:** Replaced `Object.keys(room.participants).length > 0` with `!Participants.isEmpty(room)`

#### External API (`external_api.js`)
- Replaced `Object.keys(rooms[roomItemKey].participants).length` with `rooms[roomItemKey].participants.size`
- Replaced `Object.keys(this._participants)` with `[...this._participants.keys()]`
- Replaced `Object.values(this._participants)` with `[...this._participants.values()]`
- Replaced `delete this._participants[userID]` with `this._participants.delete(userID)`
- Replaced `this._participants[userID] = value` with `this._participants.set(userID, value)`
- Replaced `this._participants[userID]` with `this._participants.get(userID)`

## Migration Patterns

### Before (Plain Object)
```typescript
// Access
const participant = room.participants[jid];

// Assignment
room.participants[jid] = participant;

// Deletion
delete room.participants[jid];

// Length
const count = Object.keys(room.participants).length;

// Iteration
Object.values(room.participants).forEach(p => {});
Object.keys(room.participants).forEach(jid => {});
```

### After (Map)
```typescript
// Access
const participant = room.participants.get(jid);

// Assignment
room.participants.set(jid, participant);

// Deletion
room.participants.delete(jid);

// Length
const count = room.participants.size;

// Iteration
Participants.values(room).forEach(p => {});
Participants.keys(room).forEach(jid => {});
```

## Benefits

1. **Type Safety:** Map provides better TypeScript support and prevents accidental property access
2. **Performance:** Map operations are generally faster for frequent additions/deletions
3. **API Consistency:** Map provides a more consistent API for key-value operations
4. **Memory Efficiency:** Map can be more memory efficient for large datasets
5. **Iteration Order:** Map preserves insertion order, which is important for UI rendering

## Testing

Added comprehensive tests in `utils.test.ts` to verify all utility functions work correctly with Map objects.

## Backward Compatibility

The migration maintains backward compatibility by:
- Providing utility functions that abstract the Map operations
- Ensuring the same data structure is returned in public APIs
- Maintaining the same interface for external consumers

## Future Considerations

1. Consider migrating other participant collections to use Map for consistency
2. Add runtime validation to ensure participants are always Map objects
3. Consider adding TypeScript strict mode checks to prevent object access patterns
10 changes: 5 additions & 5 deletions react/features/breakout-rooms/actions.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import i18next from 'i18next';
import { chunk, filter, shuffle } from 'lodash-es';

Check failure on line 2 in react/features/breakout-rooms/actions.ts

View workflow job for this annotation

GitHub Actions / Lint

There should be at least one empty line between import groups
import { Participants } from './utils';

Check failure on line 3 in react/features/breakout-rooms/actions.ts

View workflow job for this annotation

GitHub Actions / Lint

`./utils` import should occur after import of `./logger`

import { createBreakoutRoomsEvent } from '../analytics/AnalyticsEvents';
import { sendAnalytics } from '../analytics/functions';
Expand Down Expand Up @@ -70,7 +71,7 @@
sendAnalytics(createBreakoutRoomsEvent('close'));

if (room && mainRoom) {
Object.values(room.participants).forEach(p => {
Participants.values(room).forEach(p => {
dispatch(sendParticipantToRoom(p.jid, mainRoom.id));
});
}
Expand Down Expand Up @@ -113,7 +114,7 @@
return;
}

if (Object.keys(room.participants).length > 0) {
if (!Participants.isEmpty(room)) {
dispatch(closeBreakoutRoom(room.id));
}
getCurrentConference(getState)?.getBreakoutRooms()
Expand Down Expand Up @@ -307,9 +308,8 @@
const rooms = getBreakoutRooms(getState);

for (const room of Object.values(rooms)) {
const participants = room.participants || {};
const p = participants[_participantId]
|| Object.values(participants).find(item => item.jid === _participantId);
const p = room.participants?.get(_participantId)
|| Participants.values(room).find(item => item.jid === _participantId);

if (p) {
participantJid = p.jid;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import { BREAKOUT_CONTEXT_MENU_ACTIONS as ACTIONS } from '../../../participants-
import { closeBreakoutRoom, moveToRoom, removeBreakoutRoom } from '../../actions';
import { getBreakoutRoomsConfig } from '../../functions';
import { IRoom } from '../../types';
import { Participants } from '../../utils';

import BreakoutRoomNamePrompt from './BreakoutRoomNamePrompt';

Expand Down Expand Up @@ -99,7 +100,7 @@ const BreakoutRoomContextMenu = ({ room, actions = ALL_ACTIONS }: IProps) => {
}
{
!room?.isMainRoom && isLocalModerator && actions.includes(ACTIONS.REMOVE)
&& (room?.participants && Object.keys(room.participants).length > 0
&& (room?.participants && !Participants.isEmpty(room)
? <TouchableOpacity
onPress = { onCloseBreakoutRoom }
style = { styles.contextMenuItem as ViewStyle }>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { useDispatch } from 'react-redux';
import { openSheet } from '../../../base/dialog/actions';
import CollapsibleList from '../../../participants-pane/components/native/CollapsibleList';
import { IRoom } from '../../types';
import { Participants } from '../../utils';

import BreakoutRoomContextMenu from './BreakoutRoomContextMenu';
import BreakoutRoomParticipantItem from './BreakoutRoomParticipantItem';
Expand Down Expand Up @@ -36,7 +37,7 @@ export const CollapsibleRoom = ({ room, roomId }: IProps) => {
const _openContextMenu = useCallback(() => {
dispatch(openSheet(BreakoutRoomContextMenu, { room }));
}, [ room ]);
const roomParticipantsNr = Object.values(room.participants || {}).length;
const roomParticipantsNr = Participants.count(room);
const title
= `${room.name
|| t('breakoutRooms.mainRoom')} (${roomParticipantsNr})`;
Expand All @@ -46,7 +47,7 @@ export const CollapsibleRoom = ({ room, roomId }: IProps) => {
onLongPress = { _openContextMenu }
title = { title }>
<FlatList
data = { Object.values(room.participants || {}) }
data = { Participants.values(room) }
keyExtractor = { _keyExtractor }

/* @ts-ignore */
Expand Down
54 changes: 27 additions & 27 deletions react/features/breakout-rooms/functions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@

import { FEATURE_KEY } from './constants';
import { IRoom, IRoomInfo, IRoomInfoParticipant, IRooms, IRoomsInfo } from './types';
import { Participants } from './utils';

/**
* Returns the rooms object for breakout rooms.
Expand Down Expand Up @@ -94,34 +95,33 @@
} as IRoomsInfo;
}

return {
...initialRoomsInfo,
rooms: Object.keys(breakoutRooms).map(breakoutRoomKey => {
const breakoutRoomItem = breakoutRooms[breakoutRoomKey];

return {

Check failure on line 98 in react/features/breakout-rooms/functions.ts

View workflow job for this annotation

GitHub Actions / Lint

Expected indentation of 4 spaces but found 12
isMainRoom: Boolean(breakoutRoomItem.isMainRoom),
id: breakoutRoomItem.id,
jid: breakoutRoomItem.jid,
participants: breakoutRoomItem.participants && Object.keys(breakoutRoomItem.participants).length
? Object.keys(breakoutRoomItem.participants).map(participantLongId => {
const participantItem = breakoutRoomItem.participants[participantLongId];
const ids = participantLongId.split('/');
const storeParticipant = getParticipantById(stateful,
ids.length > 1 ? ids[1] : participantItem.jid);

return {
jid: participantItem?.jid,
role: participantItem?.role,
displayName: participantItem?.displayName,
avatarUrl: storeParticipant?.loadableAvatarUrl,
id: storeParticipant ? storeParticipant.id
: participantLongId
} as IRoomInfoParticipant;
}) : []
} as IRoomInfo;
})
} as IRoomsInfo;
...initialRoomsInfo,

Check failure on line 99 in react/features/breakout-rooms/functions.ts

View workflow job for this annotation

GitHub Actions / Lint

Expected indentation of 8 spaces but found 12
rooms: Object.keys(breakoutRooms).map(breakoutRoomKey => {

Check failure on line 100 in react/features/breakout-rooms/functions.ts

View workflow job for this annotation

GitHub Actions / Lint

Expected indentation of 8 spaces but found 12
const breakoutRoomItem = breakoutRooms[breakoutRoomKey];

Check failure on line 101 in react/features/breakout-rooms/functions.ts

View workflow job for this annotation

GitHub Actions / Lint

Expected indentation of 12 spaces but found 16

return {

Check failure on line 103 in react/features/breakout-rooms/functions.ts

View workflow job for this annotation

GitHub Actions / Lint

Expected indentation of 12 spaces but found 16
isMainRoom: Boolean(breakoutRoomItem.isMainRoom),

Check failure on line 104 in react/features/breakout-rooms/functions.ts

View workflow job for this annotation

GitHub Actions / Lint

Expected indentation of 16 spaces but found 20
id: breakoutRoomItem.id,
jid: breakoutRoomItem.jid,
participants: breakoutRoomItem.participants && Participants.count(breakoutRoomItem)
? Participants.values(breakoutRoomItem).map(participantItem => {
const ids = participantItem.jid.split('/');
const storeParticipant = getParticipantById(stateful,
ids.length > 1 ? ids[1] : participantItem.jid);

return {
jid: participantItem?.jid,
role: participantItem?.role,
displayName: participantItem?.displayName,
avatarUrl: storeParticipant?.loadableAvatarUrl,
id: storeParticipant ? storeParticipant.id
: participantItem.jid
} as IRoomInfoParticipant;
}) : []
} as IRoomInfo;
})
} as IRoomsInfo;
};

/**
Expand Down
24 changes: 12 additions & 12 deletions react/features/breakout-rooms/middleware.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { UPDATE_BREAKOUT_ROOMS } from './actionTypes';
import { moveToRoom } from './actions';
import logger from './logger';
import { IRooms } from './types';
import { Participants } from './utils';

/**
* Registers a change handler for state['features/base/conference'].conference to
Expand Down Expand Up @@ -52,20 +53,20 @@ MiddlewareRegistry.register(({ dispatch, getState }) => next => action => {
const newRooms: IRooms = {};

Object.entries(action.rooms as IRooms).forEach(([ key, r ]) => {
let participants = r?.participants || {};
let participants = r?.participants || new Map();
let jid;

for (const id of Object.keys(overwrittenNameList)) {
jid = Object.keys(participants).find(p => p.slice(p.indexOf('/') + 1) === id);
jid = Participants.findById(r, id)?.jid;

if (jid) {
participants = {
...participants,
[jid]: {
...participants[jid],
const participant = participants.get(jid);
if (participant) {
participants.set(jid, {
...participant,
displayName: overwrittenNameList[id as keyof typeof overwrittenNameList]
}
};
});
}
}
}

Expand All @@ -87,11 +88,10 @@ MiddlewareRegistry.register(({ dispatch, getState }) => next => action => {
const rooms: IRooms = action.rooms;

for (const room of Object.values(rooms)) {
const participants = room.participants || {};
const matchedJid = Object.keys(participants).find(jid => jid.endsWith(m.participantId));
const participant = Participants.findByPartialJid(room, m.participantId);

if (matchedJid) {
m.displayName = participants[matchedJid].displayName;
if (participant) {
m.displayName = participant.displayName;

dispatch(editMessage(m));
}
Expand Down
14 changes: 7 additions & 7 deletions react/features/breakout-rooms/types.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
export interface IParticipant {
displayName: string;
jid: string;
role: string;
}

export interface IRoom {
id: string;
isMainRoom?: boolean;
jid: string;
name: string;
participants: {
[jid: string]: {
displayName: string;
jid: string;
role: string;
};
};
participants: Map<string, IParticipant>;
}

export interface IRooms {
Expand Down
Loading
Loading