-
Notifications
You must be signed in to change notification settings - Fork 7.8k
Description
Hi! đź‘‹
Firstly, thanks for your work on this project! 🙂
Today I used patch-package to patch @jitsi/react-native-sdk@11.5.1 for the project I'm working on.
While working with @jitsi/react-native-sdk@11.5.1, I ran into a few limitations that affected moderator-based UI behavior. I was able to resolve them locally using patch-package, and I wanted to share the changes in case they’re useful to others or can be considered for upstream inclusion.
This issue body was partially generated by patch-package.
Problems Addressed
- Missing Role Change Event Support
There was no exposed onParticipantRoleChanged callback in the RN SDK wrapper.
Because of this, it was not possible to detect when a participant (including the local user) was promoted or demoted during an active conference.
- Contextless onConferenceJoined
The onConferenceJoined callback was triggered without any payload.
As a result, the app had no way to know the local participant’s role immediately after joining the conference.
- Static flags and config Props
The flags, config, and userInfo props were only applied on initial mount.
If the parent component updated these props later (for example, enabling screen sharing after the user becomes a moderator), the SDK did not react to those changes.
- Hardcoded UI Element
The “Open Shared Document” button was hardcoded in the overflow menu and could not be easily disabled or hidden via configuration.
Solution
I applied a patch that introduces:
onParticipantRoleChanged event support
Role awareness for the local participant
Payload support for onConferenceJoined
Dynamic updates for flags, config, and userInfo
Ability to hide the “Open Shared Document” menu item
Here is the diff that solved my problem:
diff --git a/node_modules/@jitsi/react-native-sdk/index.tsx b/node_modules/@jitsi/react-native-sdk/index.tsx
index 6923d91..3e7524d 100644
--- a/node_modules/@jitsi/react-native-sdk/index.tsx
+++ b/node_modules/@jitsi/react-native-sdk/index.tsx
@@ -30,10 +30,12 @@ interface IEventListeners {
onConferenceJoined?: Function;
onConferenceLeft?: Function;
onConferenceWillJoin?: Function;
+ onCustomToolbarButtonPressed?: (data: { id: string; text?: string }) => void;
onEnterPictureInPicture?: Function;
onEndpointMessageReceived?: Function;
onParticipantJoined?: Function;
onParticipantLeft?: ({ id }: { id: string }) => void;
+ onParticipantRoleChanged?: (data: { id: string; role: string }) => void;
onReadyToClose?: Function;
}
@@ -66,7 +68,7 @@ export interface JitsiRefProps {
* Main React Native SDK component that displays a Jitsi Meet conference and gets all required params as props
*/
export const JitsiMeeting = forwardRef<JitsiRefProps, IAppProps>((props, ref) => {
- const [ appProps, setAppProps ] = useState({});
+ const [appProps, setAppProps] = useState({});
const app = useRef(null);
const {
config,
@@ -140,16 +142,18 @@ export const JitsiMeeting = forwardRef<JitsiRefProps, IAppProps>((props, ref) =>
onConferenceJoined: eventListeners?.onConferenceJoined,
onConferenceWillJoin: eventListeners?.onConferenceWillJoin,
onConferenceLeft: eventListeners?.onConferenceLeft,
+ onCustomToolbarButtonPressed: eventListeners?.onCustomToolbarButtonPressed,
onEnterPictureInPicture: eventListeners?.onEnterPictureInPicture,
onEndpointMessageReceived: eventListeners?.onEndpointMessageReceived,
onParticipantJoined: eventListeners?.onParticipantJoined,
onParticipantLeft: eventListeners?.onParticipantLeft,
+ onParticipantRoleChanged: eventListeners?.onParticipantRoleChanged,
onReadyToClose: eventListeners?.onReadyToClose
},
'url': urlProps,
'userInfo': userInfo
});
- }, []
+ }, [config, flags, room, serverURL, token, userInfo, eventListeners]
);
// eslint-disable-next-line arrow-body-style
@@ -167,10 +171,10 @@ export const JitsiMeeting = forwardRef<JitsiRefProps, IAppProps>((props, ref) =>
}, []);
return (
- <View style = { style as ViewStyle }>
+ <View style={style as ViewStyle}>
<App
- { ...appProps }
- ref = { app } />
+ {...appProps}
+ ref={app} />
</View>
);
});
\ No newline at end of file
diff --git a/node_modules/@jitsi/react-native-sdk/react/features/app/components/App.native.tsx b/node_modules/@jitsi/react-native-sdk/react/features/app/components/App.native.tsx
index 93c26fc..ee5b872 100644
--- a/node_modules/@jitsi/react-native-sdk/react/features/app/components/App.native.tsx
+++ b/node_modules/@jitsi/react-native-sdk/react/features/app/components/App.native.tsx
@@ -7,6 +7,7 @@ import { hideSplash } from 'react-native-splash-view';
import BottomSheetContainer from '../../base/dialog/components/native/BottomSheetContainer';
import DialogContainer from '../../base/dialog/components/native/DialogContainer';
+import { overwriteConfig } from '../../base/config/actions';
import { updateFlags } from '../../base/flags/actions';
import { CALL_INTEGRATION_ENABLED } from '../../base/flags/constants';
import { clientResized, setSafeAreaInsets } from '../../base/responsive-ui/actions';
@@ -101,11 +102,39 @@ export class App extends AbstractApp<IProps> {
override render() {
return (
<JitsiThemePaperProvider>
- { super.render() }
+ {super.render()}
</JitsiThemePaperProvider>
);
}
+ /**
+ * Implements React Component's componentDidUpdate.
+ *
+ * @inheritdoc
+ */
+ override async componentDidUpdate(prevProps: IProps) {
+ await super.componentDidUpdate(prevProps);
+
+ const { dispatch } = this.state.store ?? {};
+ const { flags, url, userInfo } = this.props;
+ // @ts-ignore
+ const config = url?.config;
+ // @ts-ignore
+ const prevConfig = prevProps.url?.config;
+
+ if (config && JSON.stringify(prevConfig) !== JSON.stringify(config)) {
+ dispatch?.(overwriteConfig(config));
+ }
+
+ if (flags && JSON.stringify(prevProps.flags) !== JSON.stringify(flags)) {
+ dispatch?.(updateFlags(flags));
+ }
+
+ if (userInfo && JSON.stringify(prevProps.userInfo) !== JSON.stringify(userInfo)) {
+ dispatch?.(updateSettings(userInfo));
+ }
+ }
+
/**
* Initializes feature flags and updates settings.
*
@@ -185,9 +214,9 @@ export class App extends AbstractApp<IProps> {
return (
<SafeAreaProvider>
<DimensionsDetector
- onDimensionsChanged = { this._onDimensionsChanged }
- onSafeAreaInsetsChanged = { this._onSafeAreaInsetsChanged }>
- { super._createMainElement(component, props) }
+ onDimensionsChanged={this._onDimensionsChanged}
+ onSafeAreaInsetsChanged={this._onSafeAreaInsetsChanged}>
+ {super._createMainElement(component, props)}
</DimensionsDetector>
</SafeAreaProvider>
);
@@ -273,8 +302,8 @@ export class App extends AbstractApp<IProps> {
_renderDialogContainer() {
return (
<DialogContainerWrapper
- pointerEvents = 'box-none'
- style = { StyleSheet.absoluteFill }>
+ pointerEvents='box-none'
+ style={StyleSheet.absoluteFill}>
<BottomSheetContainer />
<DialogContainer />
</DialogContainerWrapper>
diff --git a/node_modules/@jitsi/react-native-sdk/react/features/mobile/react-native-sdk/middleware.js b/node_modules/@jitsi/react-native-sdk/react/features/mobile/react-native-sdk/middleware.js
index 13bf6b3..3091985 100644
--- a/node_modules/@jitsi/react-native-sdk/react/features/mobile/react-native-sdk/middleware.js
+++ b/node_modules/@jitsi/react-native-sdk/react/features/mobile/react-native-sdk/middleware.js
@@ -9,8 +9,10 @@ import {
CONFERENCE_WILL_JOIN,
ENDPOINT_MESSAGE_RECEIVED
} from '../../base/conference/actionTypes';
+import { CUSTOM_BUTTON_PRESSED } from "../../toolbox/actionTypes";
import { SET_AUDIO_MUTED, SET_VIDEO_MUTED } from '../../base/media/actionTypes';
-import { PARTICIPANT_JOINED, PARTICIPANT_LEFT } from '../../base/participants/actionTypes';
+import { PARTICIPANT_JOINED, PARTICIPANT_LEFT, PARTICIPANT_ROLE_CHANGED, PARTICIPANT_UPDATED } from '../../base/participants/actionTypes';
+import { getLocalParticipant } from '../../base/participants/functions';
import MiddlewareRegistry from '../../base/redux/MiddlewareRegistry';
import StateListenerRegistry from '../../base/redux/StateListenerRegistry';
import { READY_TO_CLOSE } from '../external-api/actionTypes';
@@ -33,57 +35,88 @@ const { JMOngoingConference } = NativeModules;
const rnSdkHandlers = getAppProp(store, 'rnSdkHandlers');
switch (type) {
- case SET_AUDIO_MUTED:
- rnSdkHandlers?.onAudioMutedChanged?.(action.muted);
- break;
- case SET_VIDEO_MUTED:
- rnSdkHandlers?.onVideoMutedChanged?.(Boolean(action.muted));
- break;
- case CONFERENCE_BLURRED:
- rnSdkHandlers?.onConferenceBlurred?.();
- break;
- case CONFERENCE_FOCUSED:
- rnSdkHandlers?.onConferenceFocused?.();
- break;
- case CONFERENCE_JOINED:
- rnSdkHandlers?.onConferenceJoined?.();
- break;
- case CONFERENCE_LEFT:
- // Props are torn down at this point, perhaps need to leave this one out
- break;
- case CONFERENCE_WILL_JOIN:
- rnSdkHandlers?.onConferenceWillJoin?.();
- break;
- case ENTER_PICTURE_IN_PICTURE:
- rnSdkHandlers?.onEnterPictureInPicture?.();
- break;
- case ENDPOINT_MESSAGE_RECEIVED: {
- const { data, participant } = action;
-
- rnSdkHandlers?.onEndpointMessageReceived?.({
- data,
- participant
- });
- break;
- }
- case PARTICIPANT_JOINED: {
- const { participant } = action;
- const participantInfo = participantToParticipantInfo(participant);
+ case SET_AUDIO_MUTED:
+ rnSdkHandlers?.onAudioMutedChanged?.(action.muted);
+ break;
+ case SET_VIDEO_MUTED:
+ rnSdkHandlers?.onVideoMutedChanged?.(Boolean(action.muted));
+ break;
+ case CONFERENCE_BLURRED:
+ rnSdkHandlers?.onConferenceBlurred?.();
+ break;
+ case CONFERENCE_FOCUSED:
+ rnSdkHandlers?.onConferenceFocused?.();
+ break;
+ case CONFERENCE_JOINED: {
+ const localParticipant = getLocalParticipant(store.getState());
+ const participantInfo = localParticipant ? participantToParticipantInfo(localParticipant) : undefined;
- rnSdkHandlers?.onParticipantJoined?.(participantInfo);
- break;
- }
- case PARTICIPANT_LEFT: {
- const { participant } = action;
+ rnSdkHandlers?.onConferenceJoined?.(participantInfo);
+ break;
+ }
+ case CONFERENCE_LEFT:
+ // Props are torn down at this point, perhaps need to leave this one out
+ break;
+ case CONFERENCE_WILL_JOIN:
+ rnSdkHandlers?.onConferenceWillJoin?.();
+ break;
+ case ENTER_PICTURE_IN_PICTURE:
+ rnSdkHandlers?.onEnterPictureInPicture?.();
+ break;
+ case ENDPOINT_MESSAGE_RECEIVED: {
+ const { data, participant } = action;
+
+ rnSdkHandlers?.onEndpointMessageReceived?.({
+ data,
+ participant
+ });
+ break;
+ }
+ case PARTICIPANT_JOINED: {
+ const { participant } = action;
+ const participantInfo = participantToParticipantInfo(participant);
+
+ rnSdkHandlers?.onParticipantJoined?.(participantInfo);
+ break;
+ }
+ case PARTICIPANT_ROLE_CHANGED: {
+ const { participant, role } = action;
- const { id } = participant ?? {};
+ if (role) {
+ const id = participant?.id;
+ const localParticipant = getLocalParticipant(store.getState());
+ const isLocal = localParticipant?.id === id;
- rnSdkHandlers?.onParticipantLeft?.({ id });
- break;
- }
- case READY_TO_CLOSE:
- rnSdkHandlers?.onReadyToClose?.();
- break;
+ rnSdkHandlers?.onParticipantRoleChanged?.({ id, role, isLocal });
+ }
+ break;
+ }
+ case PARTICIPANT_UPDATED: {
+ const { participant } = action;
+ const { id, role } = participant ?? {};
+
+ if (role) {
+ const localParticipant = getLocalParticipant(store.getState());
+ const isLocal = localParticipant?.id === id;
+
+ rnSdkHandlers?.onParticipantRoleChanged?.({ id, role, isLocal });
+ }
+ break;
+ }
+ case PARTICIPANT_LEFT: {
+ const { participant } = action;
+
+ const { id } = participant ?? {};
+
+ rnSdkHandlers?.onParticipantLeft?.({ id });
+ break;
+ }
+ case READY_TO_CLOSE:
+ rnSdkHandlers?.onReadyToClose?.();
+ break;
+ case CUSTOM_BUTTON_PRESSED:
+ rnSdkHandlers?.onCustomToolbarButtonPressed?.({ id: action.id, text: action.text });
+ break;
}
return result;
diff --git a/node_modules/@jitsi/react-native-sdk/react/features/toolbox/components/native/OverflowMenu.tsx b/node_modules/@jitsi/react-native-sdk/react/features/toolbox/components/native/OverflowMenu.tsx
index 6677519..938eaf0 100644
--- a/node_modules/@jitsi/react-native-sdk/react/features/toolbox/components/native/OverflowMenu.tsx
+++ b/node_modules/@jitsi/react-native-sdk/react/features/toolbox/components/native/OverflowMenu.tsx
@@ -153,28 +153,28 @@ class OverflowMenu extends PureComponent<IProps, IState> {
return (
<BottomSheet
- renderFooter = { this._renderReactionMenu }>
- <Divider style = { styles.divider as ViewStyle } />
- <OpenCarmodeButton { ...topButtonProps } />
- <AudioOnlyButton { ...buttonProps } />
- { this._renderRaiseHandButton(buttonProps) }
+ renderFooter={this._renderReactionMenu}>
+ <Divider style={styles.divider as ViewStyle} />
+ <OpenCarmodeButton {...topButtonProps} />
+ <AudioOnlyButton {...buttonProps} />
+ {this._renderRaiseHandButton(buttonProps)}
{/* @ts-ignore */}
- <SecurityDialogButton { ...buttonProps } />
- <RecordButton { ...buttonProps } />
- <LiveStreamButton { ...buttonProps } />
- <LinkToSalesforceButton { ...buttonProps } />
- <WhiteboardButton { ...buttonProps } />
+ <SecurityDialogButton {...buttonProps} />
+ <RecordButton {...buttonProps} />
+ <LiveStreamButton {...buttonProps} />
+ <LinkToSalesforceButton {...buttonProps} />
+ <WhiteboardButton {...buttonProps} />
{/* @ts-ignore */}
- <Divider style = { styles.divider as ViewStyle } />
- {_isSharedVideoEnabled && <SharedVideoButton { ...buttonProps } />}
- { this._renderOverflowMenuButtons(topButtonProps) }
- {!_isSpeakerStatsDisabled && <SpeakerStatsButton { ...buttonProps } />}
- {_isBreakoutRoomsSupported && <BreakoutRoomsButton { ...buttonProps } />}
+ <Divider style={styles.divider as ViewStyle} />
+ {_isSharedVideoEnabled && <SharedVideoButton {...buttonProps} />}
+ {this._renderOverflowMenuButtons(topButtonProps)}
+ {!_isSpeakerStatsDisabled && <SpeakerStatsButton {...buttonProps} />}
+ {_isBreakoutRoomsSupported && <BreakoutRoomsButton {...buttonProps} />}
{/* @ts-ignore */}
- <Divider style = { styles.divider as ViewStyle } />
- <ClosedCaptionButton { ...buttonProps } />
- <SharedDocumentButton { ...buttonProps } />
- <SettingsButton { ...buttonProps } />
+ <Divider style={styles.divider as ViewStyle} />
+ <ClosedCaptionButton {...buttonProps} />
+ {/* <SharedDocumentButton { ...buttonProps } /> */}
+ <SettingsButton {...buttonProps} />
</BottomSheet>
);
}
@@ -203,8 +203,8 @@ class OverflowMenu extends PureComponent<IProps, IState> {
if (_shouldDisplayReactionsButtons && !isRaiseHandInMainMenu) {
return (
<ReactionMenu
- onCancel = { this._onCancel }
- overflowMenu = { true } />
+ onCancel={this._onCancel}
+ overflowMenu={true} />
);
}
}
@@ -223,7 +223,7 @@ class OverflowMenu extends PureComponent<IProps, IState> {
if (!_shouldDisplayReactionsButtons && !isRaiseHandInMainMenu) {
return (
- <RaiseHandButton { ...buttonProps } />
+ <RaiseHandButton {...buttonProps} />
);
}
}
@@ -252,17 +252,17 @@ class OverflowMenu extends PureComponent<IProps, IState> {
return (
<Content
- { ...topButtonProps }
- { ...rest }
+ {...topButtonProps}
+ {...rest}
/* eslint-disable react/jsx-no-bind */
- handleClick = { () => dispatch(customButtonPressed(key, text)) }
- isToolboxButton = { false }
- key = { key }
- text = { text } />
+ handleClick={() => dispatch(customButtonPressed(key, text))}
+ isToolboxButton={false}
+ key={key}
+ text={text} />
);
})
}
- <Divider style = { styles.divider as ViewStyle } />
+ <Divider style={styles.divider as ViewStyle} />
</>
);
}
@@ -309,8 +309,8 @@ export default connect(_mapStateToProps)(props => {
<OverflowMenu
// @ts-ignore
- { ... props }
- _mainMenuButtons = { mainMenuButtons }
- _overflowMenuButtons = { overflowMenuButtons } />
+ {...props}
+ _mainMenuButtons={mainMenuButtons}
+ _overflowMenuButtons={overflowMenuButtons} />
);
});
\ No newline at end of file
This issue body was partially generated by patch-package.