Compare commits

...

16 Commits

Author SHA1 Message Date
damencho
04cac4b271 fix: Disallow visitor joining directly to main room.
When a vpaas visitor tries to join a room that has not been created and there are no main participants, we deny access.
2024-05-02 09:33:26 -05:00
Hristo Terezov
2201c4a29e fix(LargeVideo): use correct track for streaming status.
In the case where we switch from jvb to p2p when we need to switch the p2p and jvb track, they will be with the same source name. In order to add the streaming status listener we need to check if the isP2P flag is different. Without this check we won't have the correct stream status listener for the track. Normally the Thumbnail and ConnectionIndicator components will update the streaming status the same way and this may mask the problem. But if for some reason the update from the Thumbnail and ConnectionIndicator components don't happen this may lead to showing the avatar instead of the video because of the old track inactive streaming status.
2024-04-15 10:21:35 -05:00
Hristo Terezov
e93d65e1e5 feat(visitors-config): Enable media on promotion. 2024-04-12 12:10:33 -05:00
Mihaela Dumitru
b44618ed26 fix(whiteboard) backend safe room hash 2024-04-02 11:22:56 -05:00
damencho
bbbc0a761c feat: Drops unused ext_events.lua. 2024-03-27 13:34:00 -05:00
Дамян Минков
aeca535b7f feat: Reduces into state region and shard changes from the lib. (#14546)
* feat: Reduces into state region and shard changes from the lib.

* squash: Fixes few comments.

* chore(deps) lib-jitsi-meet@latest

https://github.com/jitsi/lib-jitsi-meet/compare/v1802.0.0+49ff6eb4...v1803.0.0+5237dbfe
2024-03-27 12:41:29 -05:00
Jaya Allamsetty
450db11558 fix(flags): Don't assume ssrc-rewriting enabled by default. (#14545) 2024-03-27 11:15:14 -04:00
Jaya Allamsetty
c2f1c78f6c chore(deps): update ljm, don't enable SSRC rewriting by default 2024-03-27 11:13:10 -04:00
damencho
7d0bbdc66f fix: Updates lobby password. 2024-03-26 10:49:28 -05:00
Aaron van Meerten
e5d5eee2fd fix: define local vars in public key handler (#14176) 2024-03-26 10:48:45 -05:00
bgrozev
06e4a6c543 Support multiple sip jibri prefixes. (#14497) 2024-03-26 10:48:21 -05:00
Horatiu Muresan
efb42a991d feat(external-api) Expose meeting session (#14522) 2024-03-25 16:30:38 -05:00
Hristo Terezov
a724c4faf4 fix(kick): JS error when participant pane is open.
There are cases when if you are kicked and the participant pane is
open, the getBreakoutRooms() call will return undefined and since
isBreakoutRoomRenameAllowed is used in useSelector and fails, all
execution will stop leaving us in a broken state.
2024-03-25 15:45:20 -05:00
Mihaela Dumitru
ef018f7f88 fix(whiteboard) adjust whiteboard ready check to work without config (#14486) 2024-03-25 15:42:38 -05:00
Mihaela Dumitru
c8ebd2a6e2 fix(recordings) disable default auto transcribe (#14495) 2024-03-25 15:42:05 -05:00
Saúl Ibarra Corretgé
42a0c31a3a feat(recording) add ability to change recording defaults
If recordings.recordAudioAndVideo is set to false don't record
audio-video by default.
2024-03-25 15:38:58 -05:00
27 changed files with 261 additions and 129 deletions

View File

@@ -329,6 +329,8 @@ var config = {
// configuration for all things recording related. Existing settings will be migrated here in the future.
// recordings: {
// // IF true (default) recording audio and video is selected by default in the recording dialog.
// // recordAudioAndVideo: true,
// // If true, shows a notification at the start of the meeting with a call to action button
// // to start recording (for users who can do so).
// // suggestRecording: true,

View File

@@ -981,6 +981,12 @@ function initCommands() {
callback(isP2pActive(APP.store.getState()));
break;
}
case 'session-id': {
const { conference } = APP.store.getState()['features/base/conference'];
callback(conference?.getMeetingUniqueId() || '');
break;
}
case '_new_electron_screensharing_supported': {
callback(true);
break;

View File

@@ -1229,6 +1229,17 @@ export default class JitsiMeetExternalAPI extends EventEmitter {
return this._numberOfParticipants;
}
/**
* Return the conference`s sessionId.
*
* @returns {Promise} - Resolves with the conference`s sessionId.
*/
getSessionId() {
return this._transport.sendRequest({
name: 'session-id'
});
}
/**
* Returns array of commands supported by executeCommand().
*

View File

@@ -264,7 +264,15 @@ export default class LargeVideoManager {
// in order to stop updating track streaming status for the old track and start it for the new track.
// TODO: when this class is converted to a function react component,
// use a custom hook to update a local track streaming status.
if (this.videoTrack?.jitsiTrack?.getSourceName() !== videoTrack?.jitsiTrack?.getSourceName()) {
if (this.videoTrack?.jitsiTrack?.getSourceName() !== videoTrack?.jitsiTrack?.getSourceName()
|| this.videoTrack?.jitsiTrack?.isP2P !== videoTrack?.jitsiTrack?.isP2P) {
// In the case where we switch from jvb to p2p when we need to switch the p2p and jvb track, they will be
// with the same source name. In order to add the streaming status listener we need to check if the isP2P
// flag is different. Without this check we won't have the correct stream status listener for the track.
// Normally the Thumbnail and ConnectionIndicator components will update the streaming status the same way
// and this may mask the problem. But if for some reason the update from the Thumbnail and
// ConnectionIndicator components don't happen this may lead to showing the avatar instead of
// the video because of the old track inactive streaming status.
if (this.videoTrack && !this.videoTrack.local) {
this.videoTrack.jitsiTrack.off(JitsiTrackEvents.TRACK_STREAMING_STATUS_CHANGED,
this.handleTrackStreamingStatusChanged);

27
package-lock.json generated
View File

@@ -61,7 +61,7 @@
"js-md5": "0.6.1",
"js-sha512": "0.8.0",
"jwt-decode": "2.2.0",
"lib-jitsi-meet": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v1790.0.0+311766e3/lib-jitsi-meet.tgz",
"lib-jitsi-meet": "https://github.com/jitsi/lib-jitsi-meet#release-7874",
"lodash": "4.17.21",
"moment": "2.29.4",
"moment-duration-format": "2.2.2",
@@ -6374,9 +6374,9 @@
"integrity": "sha512-esIJx9bQg+QYF0ra8GnvfianIY8qWB0GBx54PK5Eps6m+xTj86KLavHv6qDhzKcu5UUOgNfJ2pWaIIV7TRUd9Q=="
},
"node_modules/@testrtc/watchrtc-sdk": {
"version": "1.36.3",
"resolved": "https://registry.npmjs.org/@testrtc/watchrtc-sdk/-/watchrtc-sdk-1.36.3.tgz",
"integrity": "sha512-JtcTvvh20t553n8q5ZHpWQeSUTENkQrZbGNvQ05jD8SA2V5PHBok/7my1ZDuA44sgT0y1OfUA8VT7dhcs0VxWg=="
"version": "1.38.2",
"resolved": "https://registry.npmjs.org/@testrtc/watchrtc-sdk/-/watchrtc-sdk-1.38.2.tgz",
"integrity": "sha512-IlusCskjqgTrroKCr2DQCviIZYe7J3arVUpoGtXc2MiYBeQ5sEs2Yzo6v07T3rDcN71BVBTZ1hXMZlZuqnKiyA=="
},
"node_modules/@trysound/sax": {
"version": "0.2.0",
@@ -12772,8 +12772,8 @@
},
"node_modules/lib-jitsi-meet": {
"version": "0.0.0",
"resolved": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v1790.0.0+311766e3/lib-jitsi-meet.tgz",
"integrity": "sha512-rtXPegsdEOx7rxQnyxoony7BXD88ssM5prGPU2Ax6AChmzW933CZu/aW7m9bP4WSFHnVvABS3M6NEF76h282Nw==",
"resolved": "git+ssh://git@github.com/jitsi/lib-jitsi-meet.git#b1a265d046b5f51e780f4eb3f7af07b776a5a036",
"integrity": "sha512-/BCu2u6XutBEQJkhNtgGy6UREiZGkiCEEfOHekA1KXUNE0q3V/t47AYYDMPQPqZkkuv2gFK/MKLUdI49RaTYUA==",
"hasInstallScript": true,
"license": "Apache-2.0",
"dependencies": {
@@ -12781,7 +12781,7 @@
"@jitsi/logger": "2.0.2",
"@jitsi/rtcstats": "9.7.0",
"@jitsi/sdp-interop": "git+https://github.com/jitsi/sdp-interop#3d49eb4aa26863a3f8d32d7581cdb4321244266b",
"@testrtc/watchrtc-sdk": "1.36.3",
"@testrtc/watchrtc-sdk": "1.38.2",
"async-es": "3.2.4",
"base64-js": "1.3.1",
"current-executing-script": "0.1.3",
@@ -24347,9 +24347,9 @@
}
},
"@testrtc/watchrtc-sdk": {
"version": "1.36.3",
"resolved": "https://registry.npmjs.org/@testrtc/watchrtc-sdk/-/watchrtc-sdk-1.36.3.tgz",
"integrity": "sha512-JtcTvvh20t553n8q5ZHpWQeSUTENkQrZbGNvQ05jD8SA2V5PHBok/7my1ZDuA44sgT0y1OfUA8VT7dhcs0VxWg=="
"version": "1.38.2",
"resolved": "https://registry.npmjs.org/@testrtc/watchrtc-sdk/-/watchrtc-sdk-1.38.2.tgz",
"integrity": "sha512-IlusCskjqgTrroKCr2DQCviIZYe7J3arVUpoGtXc2MiYBeQ5sEs2Yzo6v07T3rDcN71BVBTZ1hXMZlZuqnKiyA=="
},
"@trysound/sax": {
"version": "0.2.0",
@@ -29183,14 +29183,15 @@
}
},
"lib-jitsi-meet": {
"version": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v1790.0.0+311766e3/lib-jitsi-meet.tgz",
"integrity": "sha512-rtXPegsdEOx7rxQnyxoony7BXD88ssM5prGPU2Ax6AChmzW933CZu/aW7m9bP4WSFHnVvABS3M6NEF76h282Nw==",
"version": "git+ssh://git@github.com/jitsi/lib-jitsi-meet.git#b1a265d046b5f51e780f4eb3f7af07b776a5a036",
"integrity": "sha512-/BCu2u6XutBEQJkhNtgGy6UREiZGkiCEEfOHekA1KXUNE0q3V/t47AYYDMPQPqZkkuv2gFK/MKLUdI49RaTYUA==",
"from": "lib-jitsi-meet@https://github.com/jitsi/lib-jitsi-meet#release-7874",
"requires": {
"@jitsi/js-utils": "2.2.1",
"@jitsi/logger": "2.0.2",
"@jitsi/rtcstats": "9.7.0",
"@jitsi/sdp-interop": "git+https://github.com/jitsi/sdp-interop#3d49eb4aa26863a3f8d32d7581cdb4321244266b",
"@testrtc/watchrtc-sdk": "1.36.3",
"@testrtc/watchrtc-sdk": "1.38.2",
"async-es": "3.2.4",
"base64-js": "1.3.1",
"current-executing-script": "0.1.3",

View File

@@ -67,7 +67,7 @@
"js-md5": "0.6.1",
"js-sha512": "0.8.0",
"jwt-decode": "2.2.0",
"lib-jitsi-meet": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v1790.0.0+311766e3/lib-jitsi-meet.tgz",
"lib-jitsi-meet": "https://github.com/jitsi/lib-jitsi-meet#release-7874",
"lodash": "4.17.21",
"moment": "2.29.4",
"moment-duration-format": "2.2.2",

View File

@@ -1,3 +1,5 @@
// @ts-expect-error
import UIEvents from '../../../../service/UI/UIEvents';
import { createStartMutedConfigurationEvent } from '../../analytics/AnalyticsEvents';
import { sendAnalytics } from '../../analytics/functions';
import { IReduxState, IStore } from '../../app/types';
@@ -7,6 +9,7 @@ import { overwriteConfig } from '../config/actions';
import { getReplaceParticipant } from '../config/functions';
import { connect, disconnect, hangup } from '../connection/actions';
import { JITSI_CONNECTION_CONFERENCE_KEY } from '../connection/constants';
import { hasAvailableDevices } from '../devices/functions.any';
import { JitsiConferenceEvents, JitsiE2ePingEvents } from '../lib-jitsi-meet';
import {
gumPending,
@@ -15,7 +18,7 @@ import {
setVideoMuted,
setVideoUnmutePermissions
} from '../media/actions';
import { MEDIA_TYPE } from '../media/constants';
import { MEDIA_TYPE, VIDEO_MUTISM_AUTHORITY } from '../media/constants';
import { IGUMPendingState } from '../media/types';
import {
dominantSpeakerChanged,
@@ -1059,13 +1062,45 @@ export function redirect(vnode: string, focusJid: string, username: string) {
.then(() => dispatch(conferenceWillInit()))
.then(() => dispatch(connect()))
.then(() => {
// Clear the gum pending state in case we have set it to pending since we are starting the
// conference without tracks.
dispatch(gumPending([ MEDIA_TYPE.AUDIO, MEDIA_TYPE.VIDEO ], IGUMPendingState.NONE));
// FIXME: Workaround for the web version. To be removed once we get rid of conference.js
if (typeof APP !== 'undefined') {
if (!vnode) {
const state = getState();
const { enableMediaOnPromote = {} } = state['features/base/config'].visitors ?? {};
const { audio = false, video = false } = enableMediaOnPromote;
if (audio) {
const { available, muted, unmuteBlocked } = state['features/base/media'].audio;
const { startSilent } = state['features/base/config'];
// do not unmute the user if he was muted before (on the prejoin, the config
// or URL param, etc.)
if (!unmuteBlocked && !muted && !startSilent && available) {
dispatch(setAudioMuted(false, true));
// // FIXME: The old conference logic still relies on this event being emitted.
typeof APP === 'undefined' || APP.UI.emitEvent(UIEvents.AUDIO_MUTED, false);
}
}
if (video) {
const { muted, unmuteBlocked } = state['features/base/media'].video;
// do not unmute the user if he was muted before (on the prejoin, the config, URL param or
// audo only, etc)
if (!unmuteBlocked && !muted && hasAvailableDevices(state, 'videoInput')) {
dispatch(setVideoMuted(false, VIDEO_MUTISM_AUTHORITY.USER, true));
// // FIXME: The old conference logic still relies on this event being emitted.
typeof APP === 'undefined' || APP.UI.emitEvent(UIEvents.VIDEO_MUTED, false);
}
}
}
APP.conference.startConference([]);
}
});

View File

@@ -496,6 +496,7 @@ export interface IConfig {
};
recordingSharingUrl?: string;
recordings?: {
recordAudioAndVideo?: boolean;
showPrejoinWarning?: boolean;
suggestRecording?: boolean;
};

View File

@@ -227,6 +227,7 @@ export default [
'useHostPageLocalStorage',
'useTurnUdp',
'videoQuality',
'visitors',
'watchRTCConfigParams',
'webrtcIceTcpDisable',
'webrtcIceUdpDisable',

View File

@@ -65,7 +65,7 @@ export function getMeetingRegion(state: IReduxState) {
* @returns {boolean}
*/
export function getSsrcRewritingFeatureFlag(state: IReduxState) {
return getFeatureFlag(state, FEATURE_FLAGS.SSRC_REWRITING) ?? true;
return getFeatureFlag(state, FEATURE_FLAGS.SSRC_REWRITING);
}
/**

View File

@@ -3,6 +3,7 @@ import _ from 'lodash';
import { CONFERENCE_INFO } from '../../conference/components/constants';
import { TOOLBAR_BUTTONS } from '../../toolbox/constants';
import { ToolbarButton } from '../../toolbox/types';
import { CONNECTION_PROPERTIES_UPDATED } from '../connection/actionTypes';
import ReducerRegistry from '../redux/ReducerRegistry';
import { equals } from '../redux/functions';
@@ -74,6 +75,12 @@ export interface IConfigState extends IConfig {
p2p?: object;
websocket?: string;
};
visitors?: {
enableMediaOnPromote?: {
audio?: boolean;
video?: boolean;
};
};
}
ReducerRegistry.register<IConfigState>('features/base/config', (state = _getInitialState(), action): IConfigState => {
@@ -94,6 +101,24 @@ ReducerRegistry.register<IConfigState>('features/base/config', (state = _getInit
locationURL: action.locationURL
};
case CONNECTION_PROPERTIES_UPDATED: {
const { region, shard } = action.properties;
const { deploymentInfo } = state;
if (deploymentInfo?.region === region && deploymentInfo?.shard === shard) {
return state;
}
return {
...state,
deploymentInfo: JSON.parse(JSON.stringify({
...deploymentInfo,
region,
shard
}))
};
}
case LOAD_CONFIG_ERROR:
// XXX LOAD_CONFIG_ERROR is one of the settlement execution paths of
// the asynchronous "loadConfig procedure/process" started with

View File

@@ -31,6 +31,16 @@ export const CONNECTION_ESTABLISHED = 'CONNECTION_ESTABLISHED';
*/
export const CONNECTION_FAILED = 'CONNECTION_FAILED';
/**
* The type of (redux) action which signals that connection properties were updated.
*
* {
* type: CONNECTION_PROPERTIES_UPDATED,
* properties: Object
* }
*/
export const CONNECTION_PROPERTIES_UPDATED = 'CONNECTION_PROPERTIES_UPDATED';
/**
* The type of (redux) action which signals that a connection will connect.
*

View File

@@ -15,6 +15,7 @@ import {
CONNECTION_DISCONNECTED,
CONNECTION_ESTABLISHED,
CONNECTION_FAILED,
CONNECTION_PROPERTIES_UPDATED,
CONNECTION_WILL_CONNECT,
SET_LOCATION_URL,
SET_PREFER_VISITOR
@@ -230,6 +231,9 @@ export function _connectInternal(id?: string, password?: string) {
connection.addEventListener(
JitsiConnectionEvents.CONNECTION_REDIRECTED,
_onConnectionRedirected);
connection.addEventListener(
JitsiConnectionEvents.PROPERTIES_UPDATED,
_onPropertiesUpdate);
/**
* Unsubscribe the connection instance from
@@ -241,6 +245,7 @@ export function _connectInternal(id?: string, password?: string) {
connection.removeEventListener(
JitsiConnectionEvents.CONNECTION_DISCONNECTED, _onConnectionDisconnected);
connection.removeEventListener(JitsiConnectionEvents.CONNECTION_FAILED, _onConnectionFailed);
connection.removeEventListener(JitsiConnectionEvents.PROPERTIES_UPDATED, _onPropertiesUpdate);
}
/**
@@ -299,7 +304,7 @@ export function _connectInternal(id?: string, password?: string) {
}
/**
* Rejects external promise when connection fails.
* Connection was redirected.
*
* @param {string|undefined} vnode - The vnode to connect to.
* @param {string} focusJid - The focus jid to use.
@@ -313,6 +318,17 @@ export function _connectInternal(id?: string, password?: string) {
dispatch(redirect(vnode, focusJid, username));
}
/**
* Connection properties were updated.
*
* @param {Object} properties - The properties which were updated.
* @private
* @returns {void}
*/
function _onPropertiesUpdate(properties: object) {
dispatch(_propertiesUpdate(properties));
}
// in case of configured http url for conference request we need the room name
const name = getBackendSafeRoomName(state['features/base/conference'].room);
@@ -343,6 +359,23 @@ function _connectionWillConnect(connection: Object) {
};
}
/**
* Create an action for when connection properties are updated.
*
* @param {Object} properties - The properties which were updated.
* @private
* @returns {{
* type: CONNECTION_PROPERTIES_UPDATED,
* properties: Object
* }}
*/
function _propertiesUpdate(properties: object) {
return {
type: CONNECTION_PROPERTIES_UPDATED,
properties
};
}
/**
* Closes connection.
*

View File

@@ -161,8 +161,7 @@ export const getCurrentRoomId = (stateful: IStateful) => {
export const isInBreakoutRoom = (stateful: IStateful) => {
const conference = getCurrentConference(stateful);
return conference?.getBreakoutRooms()
?.isBreakoutRoom();
return conference?.getBreakoutRooms()?.isBreakoutRoom();
};
/**

View File

@@ -17,7 +17,7 @@ import SharedVideo from '../../shared-video/components/web/SharedVideo';
import Captions from '../../subtitles/components/web/Captions';
import { setTileView } from '../../video-layout/actions.web';
import Whiteboard from '../../whiteboard/components/web/Whiteboard';
import { isWhiteboardReady } from '../../whiteboard/functions';
import { isWhiteboardEnabled } from '../../whiteboard/functions';
import { setSeeWhatIsBeingShared } from '../actions.web';
import { getLargeVideoParticipant } from '../functions';
@@ -112,7 +112,7 @@ interface IProps {
/**
* Whether or not the whiteboard is ready to be used.
*/
_whiteboardReady: boolean;
_whiteboardEnabled: boolean;
/**
* The Redux dispatch function.
@@ -193,7 +193,7 @@ class LargeVideo extends Component<IProps> {
_isChatOpen,
_noAutoPlayVideo,
_showDominantSpeakerBadge,
_whiteboardReady
_whiteboardEnabled
} = this.props;
const style = this._getCustomStyles();
const className = `videocontainer${_isChatOpen ? ' shift-right' : ''}`;
@@ -205,7 +205,7 @@ class LargeVideo extends Component<IProps> {
ref = { this._containerRef }
style = { style }>
<SharedVideo />
{_whiteboardReady && <Whiteboard />}
{_whiteboardEnabled && <Whiteboard />}
<div id = 'etherpad' />
<Watermarks />
@@ -378,7 +378,7 @@ function _mapStateToProps(state: IReduxState) {
_verticalFilmstripWidth: verticalFilmstripWidth.current,
_verticalViewMaxWidth: getVerticalViewMaxWidth(state),
_visibleFilmstrip: visible,
_whiteboardReady: isWhiteboardReady(state)
_whiteboardEnabled: isWhiteboardEnabled(state)
};
}

View File

@@ -305,7 +305,7 @@ export function isBreakoutRoomRenameAllowed(state: IReduxState) {
const isLocalModerator = isLocalParticipantModerator(state);
const conference = getCurrentConference(state);
const isRenameBreakoutRoomsSupported
= conference?.getBreakoutRooms().isFeatureSupported(BREAKOUT_ROOMS_RENAME_FEATURE);
= conference?.getBreakoutRooms()?.isFeatureSupported(BREAKOUT_ROOMS_RENAME_FEATURE) ?? false;
return isLocalModerator && isRenameBreakoutRoomsSupported;
}

View File

@@ -64,6 +64,11 @@ export interface IProps extends WithTranslation {
*/
_rToken: string;
/**
* Whether the record audio / video option is enabled by default.
*/
_recordAudioAndVideo: boolean;
/**
* Whether or not the local participant is screensharing.
*/
@@ -187,7 +192,7 @@ class AbstractStartRecordingDialog extends Component<IProps, IState> {
isValidating: false,
userName: undefined,
sharingEnabled: true,
shouldRecordAudioAndVideo: true,
shouldRecordAudioAndVideo: this.props._recordAudioAndVideo,
shouldRecordTranscription: this.props._autoTranscribeOnRecord,
spaceLeft: undefined,
selectedRecordingService,
@@ -452,7 +457,8 @@ export function mapStateToProps(state: IReduxState, _ownProps: any) {
const {
recordingService,
dropbox = { appKey: undefined },
localRecording
localRecording,
recordings = { recordAudioAndVideo: true }
} = state['features/base/config'];
const {
_displaySubtitles,
@@ -469,6 +475,7 @@ export function mapStateToProps(state: IReduxState, _ownProps: any) {
_isDropboxEnabled: isDropboxEnabled(state),
_localRecordingEnabled: !localRecording?.disable,
_rToken: state['features/dropbox'].rToken ?? '',
_recordAudioAndVideo: recordings?.recordAudioAndVideo ?? true,
_subtitlesLanguage,
_tokenExpireDate: state['features/dropbox'].expireDate,
_token: state['features/dropbox'].token ?? ''

View File

@@ -14,7 +14,7 @@ import { registerSound, unregisterSound } from '../base/sounds/actions';
import { isInBreakoutRoom } from '../breakout-rooms/functions';
import { isEnabled as isDropboxEnabled } from '../dropbox/functions';
import { extractFqnFromPath } from '../dynamic-branding/functions.any';
import { isRecorderTranscriptionsRunning } from '../transcribing/functions';
import { canAddTranscriber, isRecorderTranscriptionsRunning } from '../transcribing/functions';
import LocalRecordingManager from './components/Recording/LocalRecordingManager';
import {
@@ -208,7 +208,7 @@ export function canStopRecording(state: IReduxState) {
export function shouldAutoTranscribeOnRecord(state: IReduxState) {
const { transcription } = state['features/base/config'];
return transcription?.autoTranscribeOnRecord ?? true;
return (transcription?.autoTranscribeOnRecord ?? true) && canAddTranscriber(state);
}
/**

View File

@@ -6,7 +6,7 @@ import { getCurrentConference } from '../base/conference/functions';
import { IWhiteboardConfig } from '../base/config/configType';
import { getRemoteParticipants, isLocalParticipantModerator } from '../base/participants/functions';
import { encodeToBase64URL } from '../base/util/httpUtils';
import { appendURLHashParam, appendURLParam } from '../base/util/uri';
import { appendURLHashParam, appendURLParam, getBackendSafePath } from '../base/util/uri';
import { getCurrentRoomId, isInBreakoutRoom } from '../breakout-rooms/functions';
import { MIN_USER_LIMIT, USER_LIMIT_THRESHOLD, WHITEBOARD_ID, WHITEBOARD_PATH_NAME } from './constants';
@@ -36,27 +36,27 @@ export const getCollabDetails = (state: IReduxState): {
} | undefined => getWhiteboardState(state).collabDetails;
/**
* Indicates whether the whiteboard is enabled in the config.
* Indicates whether the whiteboard collaboration details are available.
*
* @param {IReduxState} state - The state from the Redux store.
* @returns {boolean}
*/
const hasCollabDetails = (state: IReduxState): boolean => Boolean(
getCollabDetails(state)?.roomId && getCollabDetails(state)?.roomKey
);
/**
* Indicates whether the whiteboard is enabled.
*
* @param {IReduxState} state - The state from the Redux store.
* @returns {boolean}
*/
export const isWhiteboardEnabled = (state: IReduxState): boolean =>
getWhiteboardConfig(state).enabled
(getWhiteboardConfig(state).enabled || hasCollabDetails(state))
&& getWhiteboardConfig(state).collabServerBaseUrl
&& getCurrentConference(state)?.getMetadataHandler()
?.isSupported();
/**
* Indicates whether the whiteboard has the collaboration
* details and is ready to be used.
*
* @param {IReduxState} state - The state from the Redux store.
* @returns {boolean}
*/
export const isWhiteboardReady = (state: IReduxState): boolean =>
isWhiteboardEnabled(state) && Boolean(getCollabDetails(state));
/**
* Indicates whether the whiteboard is open.
*
@@ -98,7 +98,9 @@ export const getCollabServerUrl = (state: IReduxState): string | undefined => {
const { locationURL } = state['features/base/connection'];
const inBreakoutRoom = isInBreakoutRoom(state);
const roomId = getCurrentRoomId(state);
const room = md5.hex(`${locationURL?.origin}${locationURL?.pathname}${inBreakoutRoom ? `|${roomId}` : ''}`);
const room = md5.hex(
`${locationURL?.origin}${getBackendSafePath(locationURL?.pathname)}${inBreakoutRoom ? `|${roomId}` : ''}`
);
return appendURLParam(collabServerBaseUrl, 'room', room);
};

View File

@@ -1,58 +0,0 @@
-- invite will perform the trigger for external call invites.
-- This trigger is left unimplemented. The implementation is expected
-- to be specific to the deployment.
local function invite(stanza, url, call_id)
module:log(
"warn",
"A module has been configured that triggers external events."
)
module:log("warn", "Implement this lib to trigger external events.")
end
-- cancel will perform the trigger for external call cancellation.
-- This trigger is left unimplemented. The implementation is expected
-- to be specific to the deployment.
local function cancel(stanza, url, reason, call_id)
module:log(
"warn",
"A module has been configured that triggers external events."
)
module:log("warn", "Implement this lib to trigger external events.")
end
-- missed will perform the trigger for external call missed notification.
-- This trigger is left unimplemented. The implementation is expected
-- to be specific to the deployment.
local function missed(stanza, call_id)
module:log(
"warn",
"A module has been configured that triggers external events."
)
module:log("warn", "Implement this lib to trigger external events.")
end
-- Event that speaker stats for a conference are available
-- this is a table where key is the jid and the value is a table:
--{
-- totalDominantSpeakerTime
-- nick
-- displayName
--}
-- This trigger is left unimplemented. The implementation is expected
-- to be specific to the deployment.
local function speaker_stats(room, speakerStats)
module:log(
"warn",
"A module has been configured that triggers external events."
)
module:log("warn", "Implement this lib to trigger external events.")
end
local ext_events = {
missed = missed,
invite = invite,
cancel = cancel,
speaker_stats = speaker_stats
}
return ext_events

View File

@@ -2,7 +2,6 @@
local st = require "util.stanza";
local socket = require "socket";
local json = require "util.json";
local ext_events = module:require "ext_events";
local it = require "util.iterators";
local process_host_module = module:require "util".process_host_module;

View File

@@ -1,4 +1,3 @@
local ext_events = module:require "ext_events"
local jid = require "util.jid"
local extract_subdomain = module:require "util".extract_subdomain;
@@ -77,13 +76,18 @@ module:hook(
local invite = function()
local url = assert(url_from_room_jid(event.stanza.attr.from))
ext_events.invite(event.stanza, url, call_id)
module:fire_event('jitsi-call-invite', { stanza = event.stanza; url = url; call_id = call_id; });
end
local cancel = function()
local url = assert(url_from_room_jid(event.stanza.attr.from))
local status = event.stanza:get_child_text("status")
ext_events.cancel(event.stanza, url, string.lower(status), call_id)
module:fire_event('jitsi-call-cancel', {
stanza = event.stanza;
url = url;
reason = string.lower(status);
call_id = call_id;
});
end
-- If for any reason call_cancel is set to true then a cancel
@@ -96,7 +100,7 @@ module:hook(
local missed = function()
cancel()
ext_events.missed(event.stanza, call_id)
module:fire_event('jitsi-call-missed', { stanza = event.stanza; call_id = call_id; });
end
-- All other call flow actions will require a status.

View File

@@ -36,6 +36,7 @@ local st = require 'util.stanza';
local muc_util = module:require "muc/util";
local valid_affiliations = muc_util.valid_affiliations;
local MUC_NS = 'http://jabber.org/protocol/muc';
local MUC_USER_NS = 'http://jabber.org/protocol/muc#user';
local DISCO_INFO_NS = 'http://jabber.org/protocol/disco#info';
local DISPLAY_NAME_REQUIRED_FEATURE = 'http://jitsi.org/protocol/lobbyrooms#displayname_required';
local LOBBY_IDENTITY_TYPE = 'lobbyrooms';
@@ -311,12 +312,29 @@ function handle_mediated_invite(room, origin, stanza, payload, host_module)
local invite = muc_util.filter_muc_x(st.clone(stanza));
invite.attr.from = room.jid;
invite.attr.to = invitee;
invite:tag('x', {xmlns='http://jabber.org/protocol/muc#user'})
invite:tag('x', { xmlns = MUC_USER_NS })
:tag('invite', {from = stanza.attr.from;})
:tag('reason'):text(payload:get_child_text("reason")):up()
:up()
:up();
if not host_module:fire_event("muc-invite", {room = room, stanza = invite, origin = origin, incoming = stanza}) then
local join = invite:get_child('x', MUC_USER_NS);
-- make sure we filter password added by any module
if join then
local password = join:get_child('password');
if password then
join:maptags(
function(tag)
for k, v in pairs(tag) do
if k == 'name' and v == 'password' then
return nil
end
end
return tag
end
);
end
end
room:route_stanza(invite);
end
return true;
@@ -336,7 +354,7 @@ local prosody_overrides = {
handle_message_to_room = function(room, origin, stanza, host_module)
local type = stanza.attr.type;
if type == nil or type == "normal" then
local x = stanza:get_child("x", "http://jabber.org/protocol/muc#user");
local x = stanza:get_child("x", MUC_USER_NS);
if x then
local handled = false;
for _, payload in pairs(x.tags) do
@@ -501,10 +519,16 @@ process_host_module(main_muc_component_config, function(host_module, host)
if not affiliation or affiliation == 'none' or affiliation == 'member' then
occupant.role = 'participant';
room:set_affiliation(true, invitee_bare_jid, 'member');
room:save_occupant(occupant);
room:save_occupant(occupant);
return;
end
elseif room:get_password() then
local affiliation = room:get_affiliation(invitee);
-- if pre-approved and password is set for the room, add the password to allow joining
if affiliation == 'member' and not password then
join:tag('password', { xmlns = MUC_NS }):text(room:get_password());
end
end
-- Check for display name if missing return an error
@@ -541,7 +565,7 @@ process_host_module(main_muc_component_config, function(host_module, host)
host_module:hook('muc-invite', function(event)
local room, stanza = event.room, event.stanza;
local invitee = stanza.attr.to;
local from = stanza:get_child('x', 'http://jabber.org/protocol/muc#user')
local from = stanza:get_child('x', MUC_USER_NS)
:get_child('invite').attr.from;
if lobby_muc_service and room._data.lobbyroom then
@@ -601,7 +625,7 @@ function handle_create_lobby(event)
-- Trigger a presence with 104 so existing participants retrieves new muc#roomconfig
room:broadcast_message(
st.message({ type='groupchat', from=room.jid })
:tag('x', { xmlns='http://jabber.org/protocol/muc#user' })
:tag('x', { xmlns = MUC_USER_NS })
:tag('status', { code='104' })
);
@@ -619,7 +643,7 @@ function handle_destroy_lobby(event)
-- Trigger a presence with 104 so existing participants retrieves new muc#roomconfig
room:broadcast_message(
st.message({ type='groupchat', from=room.jid })
:tag('x', { xmlns='http://jabber.org/protocol/muc#user' })
:tag('x', { xmlns = MUC_USER_NS })
:tag('status', { code='104' })
);
end

View File

@@ -4,7 +4,6 @@ local room_jid_match_rewrite = util.room_jid_match_rewrite;
local is_healthcheck_room = util.is_healthcheck_room;
local process_host_module = util.process_host_module;
local jid_resource = require "util.jid".resource;
local ext_events = module:require "ext_events"
local st = require "util.stanza";
local socket = require "socket";
local json = require "util.json";
@@ -314,14 +313,14 @@ function room_destroyed(event)
return;
end
ext_events.speaker_stats(room, room.speakerStats);
module:fire_event("send-speaker-stats", { room = room; roomSpeakerStats = room.speakerStats; });
end
module:hook("message/host", on_message);
function process_main_muc_loaded(main_muc, host_module)
-- the conference muc component
module:log("info", "Hook to muc events on %s", host_module);
module:log("info", "Hook to muc events on %s", host_module.host);
main_muc_service = main_muc;
module:log("info", "Main muc service %s", main_muc_service)
host_module:hook("muc-room-created", room_created, -1);
@@ -332,7 +331,7 @@ end
function process_breakout_muc_loaded(breakout_muc, host_module)
-- the Breakout muc component
module:log("info", "Hook to muc events on %s", host_module);
module:log("info", "Hook to muc events on %s", host_module.host);
host_module:hook("muc-room-created", breakout_room_created, -1);
host_module:hook("muc-occupant-joined", occupant_joined, -1);
host_module:hook("muc-occupant-pre-leave", occupant_leaving, -1);

View File

@@ -305,7 +305,7 @@ process_host_module(muc_domain_prefix..'.'..muc_domain_base, function(host_modul
-- if visitor mode is started, then you are not allowed to join without request/response exchange of iqs -> deny access
-- check list of allowed jids for the room
host_module:hook('muc-occupant-pre-join', function (event)
local room, stanza, occupant, origin = event.room, event.stanza, event.occupant, event.origin;
local room, stanza, occupant, session = event.room, event.stanza, event.occupant, event.origin;
if is_healthcheck_room(room.jid) or is_admin(occupant.bare_jid) then
return;
@@ -334,9 +334,17 @@ process_host_module(muc_domain_prefix..'.'..muc_domain_base, function(host_modul
-- allow join
return;
end
origin.send(st.error_reply(stanza, 'cancel', 'not-allowed', 'Visitor needs to be allowed by a moderator'));
module:log('error', 'Visitor needs to be allowed by a moderator %s', stanza.attr.from);
session.send(st.error_reply(stanza, 'cancel', 'not-allowed', 'Visitor needs to be allowed by a moderator'));
return true;
elseif is_vpaas(room) then
-- special case for vpaas where if someone with a visitor token tries to join a room, where
-- there are no visitors yet, we deny access
if session.jitsi_meet_context_user and session.jitsi_meet_context_user.role == 'visitor' then
session.log('warn', 'Deny user join as visitor in the main meeting, not approved');
session.send(st.error_reply(
stanza, 'cancel', 'not-allowed', 'Visitor tried to join the main room without approval'));
end
end
end, 7); -- after muc_meeting_id, the logic for not joining before jicofo

View File

@@ -123,6 +123,7 @@ function Util.new(module)
self.cachedKeys = {};
local update_keys_cache;
update_keys_cache = async.runner(function (name)
local content, code, cache_for;
content, code, cache_for = http_get_with_retry(self.cacheKeysUrl, nr_retries);
if content ~= nil then
local keys_to_delete = table_shallow_copy(self.cachedKeys);

View File

@@ -26,8 +26,8 @@ local target_subdomain_pattern = "^"..escaped_muc_domain_prefix..".([^%.]+)%."..
-- table to store all incoming iqs without roomname in it, like discoinfo to the muc component
local roomless_iqs = {};
local OUTBOUND_SIP_JIBRI_PREFIX = 'outbound-sip-jibri@';
local INBOUND_SIP_JIBRI_PREFIX = 'inbound-sip-jibri@';
local OUTBOUND_SIP_JIBRI_PREFIXES = { 'outbound-sip-jibri@', 'sipjibriouta@', 'sipjibrioutb@' };
local INBOUND_SIP_JIBRI_PREFIXES = { 'inbound-sip-jibri@', 'sipjibriina@', 'sipjibriina@' };
local split_subdomain_cache = cache.new(1000);
local extract_subdomain_cache = cache.new(1000);
@@ -281,6 +281,19 @@ function starts_with(str, start)
return str:sub(1, #start) == start
end
function starts_with_one_of(str, prefixes)
if not str then
return false;
end
for i=1,#prefixes do
if starts_with(str, prefixes[i]) then
return prefixes[i];
end
end
return false
end
function ends_with(str, ending)
return ending == "" or str:sub(-#ending) == ending
end
@@ -456,10 +469,10 @@ end
function get_sip_jibri_email_prefix(email)
if not email then
return nil;
elseif starts_with(email, INBOUND_SIP_JIBRI_PREFIX) then
return INBOUND_SIP_JIBRI_PREFIX;
elseif starts_with(email, OUTBOUND_SIP_JIBRI_PREFIX) then
return OUTBOUND_SIP_JIBRI_PREFIX;
elseif starts_with_one_of(email, INBOUND_SIP_JIBRI_PREFIXES) then
return starts_with_one_of(email, INBOUND_SIP_JIBRI_PREFIXES);
elseif starts_with_one_of(email, OUTBOUND_SIP_JIBRI_PREFIXES) then
return starts_with_one_of(email, OUTBOUND_SIP_JIBRI_PREFIXES);
else
return nil;
end
@@ -518,8 +531,8 @@ function table_shallow_copy(t)
end
return {
OUTBOUND_SIP_JIBRI_PREFIX = OUTBOUND_SIP_JIBRI_PREFIX;
INBOUND_SIP_JIBRI_PREFIX = INBOUND_SIP_JIBRI_PREFIX;
OUTBOUND_SIP_JIBRI_PREFIXES = OUTBOUND_SIP_JIBRI_PREFIXES;
INBOUND_SIP_JIBRI_PREFIXES = INBOUND_SIP_JIBRI_PREFIXES;
extract_subdomain = extract_subdomain;
is_feature_allowed = is_feature_allowed;
is_healthcheck_room = is_healthcheck_room;
@@ -540,5 +553,6 @@ return {
http_get_with_retry = http_get_with_retry;
ends_with = ends_with;
starts_with = starts_with;
starts_with_one_of = starts_with_one_of;
table_shallow_copy = table_shallow_copy;
};