Compare commits

...

4 Commits

Author SHA1 Message Date
Saúl Ibarra Corretgé
f6f4ebf185 fix(recording) prevent multiple consent requests
A given recording should only trigger a single consent request.

The mechanism to notify about recording status updates may fire multiple
times since it's tied to XMPP presence and may send updates such as when
the live stream view URL is set.

Rather than trying to handle all possible corner cases to make sure we
only show the consent dialog once, keep track of the recording session
IDs for which we _have_ asked for consent and skip the dialog in case we
have done it already.
2025-04-30 15:30:51 +02:00
Calinteodor
b500c9dcde fix(base/connection/native): add a check for vpass meeting when we connect (#15978)
When we connect to a VPASS meeting on mobile we need to check for a couple of things.
2025-04-30 15:16:25 +03:00
Hristo Terezov
d5670a2b4f chore(deps) lib-jitsi-meet@latest
https://github.com/jitsi/lib-jitsi-meet/compare/v1980.0.0+34a32e86...v1982.0.0+cec2a2e6
2025-04-29 21:16:21 -05:00
Saúl Ibarra Corretgé
ee3f82bf0c feat(external_api,devices) drop use of isDeviceListAvailable
It's always true.
2025-04-29 19:37:55 +02:00
13 changed files with 89 additions and 33 deletions

View File

@@ -2069,8 +2069,7 @@ export default {
_initDeviceList(setDeviceListChangeHandler = false) {
const { mediaDevices } = JitsiMeetJS;
if (mediaDevices.isDeviceListAvailable()
&& mediaDevices.isDeviceChangeAvailable()) {
if (mediaDevices.isDeviceChangeAvailable()) {
if (setDeviceListChangeHandler) {
this.deviceChangeListener = devices =>
window.setTimeout(() => this._onDeviceListChanged(devices), 0);

View File

@@ -11,7 +11,6 @@ import {
getAvailableDevices,
getCurrentDevices,
isDeviceChangeAvailable,
isDeviceListAvailable,
isMultipleAudioInputSupported,
setAudioInputDevice,
setAudioOutputDevice,
@@ -989,10 +988,15 @@ export default class JitsiMeetExternalAPI extends EventEmitter {
* Returns Promise that resolves with true if the device list is available
* and with false if not.
*
* @deprecated
*
* @returns {Promise}
*/
isDeviceListAvailable() {
return isDeviceListAvailable(this._transport);
console.warn('isDeviceListAvailable is deprecated and will be removed in the future. '
+ 'It always returns true');
return Promise.resolve(true);
}
/**

View File

@@ -56,21 +56,6 @@ export function isDeviceChangeAvailable(transport, deviceType) {
});
}
/**
* Returns Promise that resolves with true if the device list is available
* and with false if not.
*
* @param {Transport} transport - The @code{Transport} instance responsible for
* the external communication.
* @returns {Promise}
*/
export function isDeviceListAvailable(transport) {
return transport.sendRequest({
type: 'devices',
name: 'isDeviceListAvailable'
});
}
/**
* Returns Promise that resolves with true if multiple audio input is supported
* and with false if not.

10
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/v1980.0.0+34a32e86/lib-jitsi-meet.tgz",
"lib-jitsi-meet": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v1982.0.0+cec2a2e6/lib-jitsi-meet.tgz",
"lodash-es": "4.17.21",
"moment": "2.29.4",
"moment-duration-format": "2.2.2",
@@ -16982,8 +16982,8 @@
},
"node_modules/lib-jitsi-meet": {
"version": "0.0.0",
"resolved": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v1980.0.0+34a32e86/lib-jitsi-meet.tgz",
"integrity": "sha512-NmjVrkhBgUhAHe84oEVGi5keXmO92RtVznchdPep6vJz9O2A6GPN9Ap+DZOoiK693bm9lRdzDIEIFn5GnlLfQg==",
"resolved": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v1982.0.0+cec2a2e6/lib-jitsi-meet.tgz",
"integrity": "sha512-lptKoClX5zMGocOJwTllNcV7BalPlWyeaTWixCeuiOp/03JTXgIVAJJ0fG/s4BhP4YRd1YBqttnKBo/ly7NZWw==",
"license": "Apache-2.0",
"dependencies": {
"@jitsi/js-utils": "2.2.1",
@@ -37440,8 +37440,8 @@
}
},
"lib-jitsi-meet": {
"version": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v1980.0.0+34a32e86/lib-jitsi-meet.tgz",
"integrity": "sha512-NmjVrkhBgUhAHe84oEVGi5keXmO92RtVznchdPep6vJz9O2A6GPN9Ap+DZOoiK693bm9lRdzDIEIFn5GnlLfQg==",
"version": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v1982.0.0+cec2a2e6/lib-jitsi-meet.tgz",
"integrity": "sha512-lptKoClX5zMGocOJwTllNcV7BalPlWyeaTWixCeuiOp/03JTXgIVAJJ0fG/s4BhP4YRd1YBqttnKBo/ly7NZWw==",
"requires": {
"@jitsi/js-utils": "2.2.1",
"@jitsi/logger": "2.0.2",

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/v1980.0.0+34a32e86/lib-jitsi-meet.tgz",
"lib-jitsi-meet": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v1982.0.0+cec2a2e6/lib-jitsi-meet.tgz",
"lodash-es": "4.17.21",
"moment": "2.29.4",
"moment-duration-format": "2.2.2",

View File

@@ -1,10 +1,13 @@
import { appNavigate } from '../../app/actions.native';
import { IStore } from '../../app/types';
import { getCustomerDetails } from '../../jaas/actions.any';
import { isVpaasMeeting, getJaasJWT } from '../../jaas/functions';
import { navigateRoot } from '../../mobile/navigation/rootNavigationContainerRef';
import { screen } from '../../mobile/navigation/routes';
import { setJWT } from '../jwt/actions';
import { JitsiConnectionErrors } from '../lib-jitsi-meet';
import { _connectInternal } from './actions.any';
import { _connectInternal } from './actions.native';
export * from './actions.any';
@@ -16,12 +19,32 @@ export * from './actions.any';
* @returns {Function}
*/
export function connect(id?: string, password?: string) {
return (dispatch: IStore['dispatch']) => dispatch(_connectInternal(id, password))
return (dispatch: IStore['dispatch'], getState: IStore['getState']) => {
const state = getState();
const { jwt } = state['features/base/jwt'];
if (isVpaasMeeting(state)) {
return dispatch(getCustomerDetails())
.then(() => {
if (!jwt) {
return getJaasJWT(state);
}
})
.then(j => {
j && dispatch(setJWT(j));
return dispatch(_connectInternal(id, password));
});
}
dispatch(_connectInternal(id, password))
.catch(error => {
if (error === JitsiConnectionErrors.NOT_LIVE_ERROR) {
navigateRoot(screen.visitorsQueue);
}
});
};
}
/**

View File

@@ -150,8 +150,7 @@ export function getAvailableDevices() {
return (dispatch: IStore['dispatch'], getState: IStore['getState']) => new Promise(resolve => {
const { mediaDevices } = JitsiMeetJS;
if (mediaDevices.isDeviceListAvailable()
&& mediaDevices.isDeviceChangeAvailable()) {
if (mediaDevices.isDeviceChangeAvailable()) {
mediaDevices.enumerateDevices((devices: MediaDeviceInfo[]) => {
const { filteredDevices, ignoredDevices } = filterIgnoredDevices(devices);
const oldDevices = flattenAvailableDevices(getState()['features/base/devices'].availableDevices);

View File

@@ -163,7 +163,8 @@ export function processExternalDeviceRequest( // eslint-disable-line max-params
switch (request.name) {
case 'isDeviceListAvailable':
responseCallback(JitsiMeetJS.mediaDevices.isDeviceListAvailable());
// TODO(saghul): remove this, eventually.
responseCallback(true);
break;
case 'isDeviceChangeAvailable':
responseCallback(

View File

@@ -8,6 +8,16 @@
*/
export const CLEAR_RECORDING_SESSIONS = 'CLEAR_RECORDING_SESSIONS';
/**
* The type of Redux action which marks a session ID as consent requested.
*
* {
* type: MARK_CONSENT_REQUESTED,
* sessionId: string
* }
*/
export const MARK_CONSENT_REQUESTED = 'MARK_CONSENT_REQUESTED';
/**
* The type of Redux action which updates the current known state of a recording
* session.

View File

@@ -20,6 +20,7 @@ import { isRecorderTranscriptionsRunning } from '../transcribing/functions';
import {
CLEAR_RECORDING_SESSIONS,
MARK_CONSENT_REQUESTED,
RECORDING_SESSION_UPDATED,
SET_MEETING_HIGHLIGHT_BUTTON_STATE,
SET_PENDING_RECORDING_NOTIFICATION_UID,
@@ -476,3 +477,17 @@ export function showStartRecordingNotificationWithCallback(openRecordingDialog:
}, NOTIFICATION_TIMEOUT_TYPE.EXTRA_LONG));
};
}
/**
* Marks the given session as consent requested. No further consent requests will be
* made for this session.
*
* @param {string} sessionId - The session id.
* @returns {Object}
*/
export function markConsentRequested(sessionId: string) {
return {
type: MARK_CONSENT_REQUESTED,
sessionId
};
}

View File

@@ -442,6 +442,7 @@ export function shouldRequireRecordingConsent(recorderSession: any, state: IRedu
const { requireRecordingConsent } = state['features/dynamic-branding'] || {};
const { requireConsent } = state['features/base/config'].recordings || {};
const { iAmRecorder } = state['features/base/config'];
const { consentRequested } = state['features/recording'];
if (iAmRecorder) {
return false;
@@ -455,10 +456,15 @@ export function shouldRequireRecordingConsent(recorderSession: any, state: IRedu
return false;
}
if (!recorderSession.getInitiator()
|| recorderSession.getStatus() === JitsiRecordingConstants.status.OFF) {
if (consentRequested.has(recorderSession.getID())) {
return false;
}
return recorderSession.getInitiator() !== getLocalParticipant(state)?.id;
const initiator = recorderSession.getInitiator();
if (!initiator || recorderSession.getStatus() === JitsiRecordingConstants.status.OFF) {
return false;
}
return initiator !== getLocalParticipant(state)?.id;
}

View File

@@ -36,6 +36,7 @@ import { isRecorderTranscriptionsRunning } from '../transcribing/functions';
import { RECORDING_SESSION_UPDATED, START_LOCAL_RECORDING, STOP_LOCAL_RECORDING } from './actionTypes';
import {
clearRecordingSessions,
markConsentRequested,
hidePendingRecordingNotification,
showPendingRecordingNotification,
showRecordingError,
@@ -420,6 +421,7 @@ function _showExplicitConsentDialog(recorderSession: any, dispatch: IStore['disp
}
batch(() => {
dispatch(markConsentRequested(recorderSession.getID()));
dispatch(setAudioUnmutePermissions(true, true));
dispatch(setVideoUnmutePermissions(true, true));
dispatch(setAudioMuted(true));

View File

@@ -2,6 +2,7 @@ import ReducerRegistry from '../base/redux/ReducerRegistry';
import {
CLEAR_RECORDING_SESSIONS,
MARK_CONSENT_REQUESTED,
RECORDING_SESSION_UPDATED,
SET_MEETING_HIGHLIGHT_BUTTON_STATE,
SET_PENDING_RECORDING_NOTIFICATION_UID,
@@ -11,6 +12,7 @@ import {
} from './actionTypes';
const DEFAULT_STATE = {
consentRequested: new Set(),
disableHighlightMeetingMoment: false,
pendingNotificationUids: {},
selectedRecordingService: '',
@@ -29,6 +31,7 @@ export interface ISessionData {
}
export interface IRecordingState {
consentRequested: Set<any>;
disableHighlightMeetingMoment: boolean;
pendingNotificationUids: {
[key: string]: string | undefined;
@@ -57,6 +60,15 @@ ReducerRegistry.register<IRecordingState>(STORE_NAME,
sessionDatas: []
};
case MARK_CONSENT_REQUESTED:
return {
...state,
consentRequested: new Set([
...state.consentRequested,
action.sessionId
])
};
case RECORDING_SESSION_UPDATED:
return {
...state,