Compare commits

..

9 Commits

Author SHA1 Message Date
Jaya Allamsetty
23cc91391b fix(tracks) Replace the tracks directly on camera toggle.
Fixes an issue where p2p peer stops rendering remote video when the mobile client toggles camera. This happens only when the peer starts video muted.
2025-02-13 15:25:23 -05:00
Hristo Terezov
737d24824a fix(config): Allow only enableMediaOnPromote from visitors config to be overriden. 2025-01-16 09:02:32 -06:00
Jaya Allamsetty
bff8bbb624 chore(deps) update to release branch release-8302 for LJM. 2025-01-08 14:16:32 -05:00
damencho
d5b76c1abf fix(prosody): Adds another condition to the filter. 2025-01-08 12:02:36 -06:00
Hristo Terezov
73505d56d7 feat(customParticipantButton): metrics 2025-01-08 11:13:01 -06:00
Hristo Terezov
c426f96943 feat(URL-overrides): Add metrics. 2025-01-07 16:16:38 -06:00
damencho
2beea0a952 fix(shared-video): Gets from info from the incoming presence.
Ignore using from field send in attributes of the command.

Remove disable button action from web.
2024-12-23 10:55:36 -06:00
damencho
52b2444749 fix(polls): Returns an error on duplicate poll. 2024-12-17 16:45:34 -06:00
damencho
7ce85a3e83 fix(visitors): Fix a check that can result missing main participants. 2024-12-17 11:01:00 -06:00
246 changed files with 4057 additions and 10950 deletions

View File

@@ -53,8 +53,6 @@ jobs:
npm -v
- run: npm install
- run: make
- name: Check config.js syntax
run: node config.js
android-rn-bundle-build:
name: Build mobile bundle (Android)
runs-on: macos-15

3
.gitignore vendored
View File

@@ -101,9 +101,6 @@ tsconfig.json
react-native-sdk/*.tgz
react-native-sdk/android/src
!react-native-sdk/android/src/main/java/org/jitsi/meet/sdk/JitsiMeetReactNativePackage.java
!react-native-sdk/android/src/main/java/org/jitsi/meet/sdk/JitsiMeetOngoingConferenceService.java
!react-native-sdk/android/src/main/java/org/jitsi/meet/sdk/JMOngoingConferenceModule.java
!react-native-sdk/android/src/main/java/org/jitsi/meet/sdk/RNOngoingNotification.java
react-native-sdk/images
react-native-sdk/ios
react-native-sdk/lang

2
.nvmrc
View File

@@ -1 +1 @@
22
20

View File

@@ -17,11 +17,11 @@ STYLES_BUNDLE = css/all.bundle.css
STYLES_DESTINATION = css/all.css
STYLES_MAIN = css/main.scss
ifeq ($(OS),Windows_NT)
WEBPACK = .\node_modules\.bin\webpack --progress
WEBPACK_DEV_SERVER = .\node_modules\.bin\webpack serve --mode development --progress
WEBPACK = .\node_modules\.bin\webpack
WEBPACK_DEV_SERVER = .\node_modules\.bin\webpack serve --mode development
else
WEBPACK = ./node_modules/.bin/webpack --progress
WEBPACK_DEV_SERVER = ./node_modules/.bin/webpack serve --mode development --progress
WEBPACK = ./node_modules/.bin/webpack
WEBPACK_DEV_SERVER = ./node_modules/.bin/webpack serve --mode development
endif
all: compile deploy

View File

@@ -34,7 +34,7 @@ mobile apps:
| Android | Android (F-Droid) | iOS |
|:-:|:-:|:-:|
| [<img src="resources/img/google-play-badge.png" height="50">](https://play.google.com/store/apps/details?id=org.jitsi.meet) | [<img src="resources/img/f-droid-badge.png" height="50">](https://f-droid.org/packages/org.jitsi.meet/) | [<img src="resources/img/appstore-badge.png" height="50">](https://itunes.apple.com/us/app/jitsi-meet/id1165103905) |
| [<img src="resources/img/google-play-badge.png" height="50">](https://play.google.com/store/apps/details?id=org.jitsi.meet) | [<img src="resources/img/f-droid-badge.png" height="50">](https://f-droid.org/en/packages/org.jitsi.meet/) | [<img src="resources/img/appstore-badge.png" height="50">](https://itunes.apple.com/us/app/jitsi-meet/id1165103905) |
If you are feeling adventurous and want to get an early scoop of the features as they are being
developed you can also sign up for our open beta testing here:

View File

@@ -20,7 +20,7 @@ ext {
kotlinVersion = "1.9.24"
buildToolsVersion = "34.0.0"
compileSdkVersion = 34
minSdkVersion = 26
minSdkVersion = 24
targetSdkVersion = 34
supportLibVersion = "28.0.0"
ndkVersion = "26.1.10909125"

View File

@@ -18,6 +18,7 @@ package org.jitsi.meet.sdk;
import android.content.Context;
import android.media.AudioManager;
import android.os.Build;
import android.telecom.CallAudioState;
import androidx.annotation.RequiresApi;
@@ -33,6 +34,7 @@ import org.jitsi.meet.sdk.log.JitsiMeetLogger;
* {@link AudioModeModule.AudioDeviceHandlerInterface} module implementing device handling for
* Android versions >= O when ConnectionService is enabled.
*/
@RequiresApi(Build.VERSION_CODES.O)
class AudioDeviceHandlerConnectionService implements
AudioModeModule.AudioDeviceHandlerInterface,
RNConnectionService.CallAudioStateListener {

View File

@@ -20,6 +20,7 @@ import android.media.AudioAttributes;
import android.media.AudioDeviceInfo;
import android.media.AudioFocusRequest;
import android.media.AudioManager;
import android.os.Build;
import java.util.HashSet;
import java.util.Set;
@@ -226,17 +227,22 @@ class AudioDeviceHandlerGeneric implements
audioManager.setMode(AudioManager.MODE_IN_COMMUNICATION);
audioManager.setMicrophoneMute(false);
int gotFocus = audioManager.requestAudioFocus(new AudioFocusRequest.Builder(AudioManager.AUDIOFOCUS_GAIN)
.setAudioAttributes(
new AudioAttributes.Builder()
.setUsage(AudioAttributes.USAGE_VOICE_COMMUNICATION)
.setContentType(AudioAttributes.CONTENT_TYPE_SPEECH)
.build()
)
.setAcceptsDelayedFocusGain(true)
.setOnAudioFocusChangeListener(this)
.build()
);
int gotFocus;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
gotFocus = audioManager.requestAudioFocus(new AudioFocusRequest.Builder(AudioManager.AUDIOFOCUS_GAIN)
.setAudioAttributes(
new AudioAttributes.Builder()
.setUsage(AudioAttributes.USAGE_VOICE_COMMUNICATION)
.setContentType(AudioAttributes.CONTENT_TYPE_SPEECH)
.build()
)
.setAcceptsDelayedFocusGain(true)
.setOnAudioFocusChangeListener(this)
.build()
);
} else {
gotFocus = audioManager.requestAudioFocus(this, AudioManager.STREAM_VOICE_CALL, AudioManager.AUDIOFOCUS_GAIN);
}
if (gotFocus == AudioManager.AUDIOFOCUS_REQUEST_FAILED) {
JitsiMeetLogger.w(TAG + " Audio focus request failed");

View File

@@ -20,6 +20,7 @@ import android.annotation.SuppressLint;
import android.app.Activity;
import android.content.Context;
import android.media.AudioManager;
import android.os.Build;
import com.facebook.react.bridge.Arguments;
import com.facebook.react.bridge.Promise;
@@ -57,6 +58,7 @@ import java.util.concurrent.Executors;
* Before a call has started and after it has ended the
* {@code AudioModeModule.DEFAULT} mode should be used.
*/
@SuppressLint("AnnotateVersionCheck")
@ReactModule(name = AudioModeModule.NAME)
class AudioModeModule extends ReactContextBaseJavaModule {
public static final String NAME = "AudioMode";
@@ -82,10 +84,11 @@ class AudioModeModule extends ReactContextBaseJavaModule {
/**
* Whether or not the ConnectionService is used for selecting audio devices.
*/
private static boolean useConnectionService_ = true;
private static final boolean supportsConnectionService = Build.VERSION.SDK_INT >= Build.VERSION_CODES.O;
private static boolean useConnectionService_ = supportsConnectionService;
static boolean useConnectionService() {
return useConnectionService_;
return supportsConnectionService && useConnectionService_;
}
/**
@@ -136,11 +139,6 @@ class AudioModeModule extends ReactContextBaseJavaModule {
*/
private String userSelectedDevice;
/**
* Whether or not audio is disabled.
*/
private boolean audioDisabled;
/**
* Initializes a new module instance. There shall be a single instance of
* this module throughout the lifetime of the application.
@@ -241,12 +239,6 @@ class AudioModeModule extends ReactContextBaseJavaModule {
audioDeviceHandler.stop();
}
audioDeviceHandler = null;
if (audioDisabled) {
return;
}
if (useConnectionService()) {
audioDeviceHandler = new AudioDeviceHandlerConnectionService(audioManager);
} else {
@@ -289,27 +281,6 @@ class AudioModeModule extends ReactContextBaseJavaModule {
});
}
@ReactMethod
public void setDisabled(final boolean disabled, final Promise promise) {
if (audioDisabled == disabled) {
promise.resolve(null);
return;
}
JitsiMeetLogger.i(TAG + " audio disabled: " + disabled);
audioDisabled = disabled;
setAudioDeviceHandler();
if (disabled) {
mode = -1;
availableDevices.clear();
resetSelectedDevice();
}
promise.resolve(null);
}
/**
* Public method to set the current audio mode.
*
@@ -319,12 +290,7 @@ class AudioModeModule extends ReactContextBaseJavaModule {
*/
@ReactMethod
public void setMode(final int mode, final Promise promise) {
if (audioDisabled) {
promise.resolve(null);
return;
}
if (mode < DEFAULT || mode > VIDEO_CALL) {
if (mode != DEFAULT && mode != AUDIO_CALL && mode != VIDEO_CALL) {
promise.reject("setMode", "Invalid audio mode " + mode);
return;
}

View File

@@ -37,6 +37,7 @@ import java.util.Objects;
*
* @author Pawel Domas
*/
@RequiresApi(api = Build.VERSION_CODES.O)
public class ConnectionService extends android.telecom.ConnectionService {
/**

View File

@@ -271,8 +271,8 @@ public class JitsiMeetActivity extends AppCompatActivity
// JitsiMeetLogger.i("Transcription chunk received: " + extraData);
// }
// protected void onCustomButtonPressed(HashMap<String, Object> extraData) {
// JitsiMeetLogger.i("Custom button pressed: " + extraData);
// protected void onCustomOverflowMenuButtonPressed(HashMap<String, Object> extraData) {
// JitsiMeetLogger.i("Custom overflow menu button pressed: " + extraData);
// }
// Activity lifecycle methods
@@ -361,8 +361,8 @@ public class JitsiMeetActivity extends AppCompatActivity
// case TRANSCRIPTION_CHUNK_RECEIVED:
// onTranscriptionChunkReceived(event.getData());
// break;
// case CUSTOM_BUTTON_PRESSED:
// onCustomButtonPressed(event.getData());
// case CUSTOM_OVERFLOW_MENU_BUTTON_PRESSED:
// onCustomOverflowMenuButtonPressed(event.getData());
// break;
}
}

View File

@@ -42,7 +42,7 @@ public class JitsiMeetActivityDelegate {
/**
* Tells whether or not the permissions request is currently in progress.
*
* @return {@code true} if the permissions are being requested or {@code false} otherwise.
* @return {@code true} if the permssions are being requested or {@code false} otherwise.
*/
static boolean arePermissionsBeingRequested() {
return permissionListener != null;

View File

@@ -83,7 +83,11 @@ public class JitsiMeetOngoingConferenceService extends Service implements Ongoin
ComponentName componentName;
try {
componentName = context.startForegroundService(intent);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
componentName = context.startForegroundService(intent);
} else {
componentName = context.startService(intent);
}
} catch (RuntimeException e) {
// Avoid crashing due to ForegroundServiceStartNotAllowedException (API level 31).
// See: https://developer.android.com/guide/components/foreground-services#background-start-restrictions

View File

@@ -29,6 +29,8 @@ import android.content.Intent;
import androidx.annotation.StringRes;
import androidx.core.app.NotificationCompat;
import android.os.Build;
/**
* Helper class for creating the ongoing notification which is used with
@@ -43,6 +45,10 @@ class OngoingNotification {
static final String ONGOING_CONFERENCE_CHANNEL_ID = "JitsiOngoingConferenceChannel";
static void createNotificationChannel(Activity context) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
return;
}
if (context == null) {
JitsiMeetLogger.w(TAG + " Cannot create notification channel: no current context");
return;

View File

@@ -20,6 +20,7 @@ import android.annotation.TargetApi;
import android.app.Activity;
import android.app.ActivityManager;
import android.app.PictureInPictureParams;
import android.os.Build;
import android.util.Rational;
import com.facebook.react.bridge.Promise;
@@ -52,7 +53,7 @@ class PictureInPictureModule extends ReactContextBaseJavaModule {
// Android Go devices don't support PiP. There doesn't seem to be a better way to detect it than
// to use ActivityManager.isLowRamDevice().
// https://stackoverflow.com/questions/58340558/how-to-detect-android-go
isSupported = !am.isLowRamDevice();
isSupported = Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && !am.isLowRamDevice();
}
/**
@@ -81,6 +82,7 @@ class PictureInPictureModule extends ReactContextBaseJavaModule {
* including when the activity is not visible (paused or stopped), if the
* screen is locked or if the user has an activity pinned.
*/
@TargetApi(Build.VERSION_CODES.O)
public void enterPictureInPicture() {
if (!isEnabled) {
return;

View File

@@ -3,6 +3,7 @@ package org.jitsi.meet.sdk;
import android.annotation.SuppressLint;
import android.content.Context;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.telecom.DisconnectCause;
import android.telecom.PhoneAccount;
@@ -31,6 +32,7 @@ import org.jitsi.meet.sdk.log.JitsiMeetLogger;
*
* @author Pawel Domas
*/
@RequiresApi(api = Build.VERSION_CODES.O)
@ReactModule(name = RNConnectionService.NAME)
class RNConnectionService extends ReactContextBaseJavaModule {
@@ -51,6 +53,7 @@ class RNConnectionService extends ReactContextBaseJavaModule {
* @param audioRoute the new audio route to be set. See
* {@link android.telecom.CallAudioState} constants prefixed with "ROUTE_".
*/
@RequiresApi(api = Build.VERSION_CODES.O)
static void setAudioRoute(int audioRoute) {
for (ConnectionService.ConnectionImpl c
: ConnectionService.getConnections()) {

6
app.js
View File

@@ -1,8 +1,4 @@
/* Jitsi Meet app main entrypoint. */
// Polyfill Promise.withReolvers.
// FIXME(saghul) webpack + core-js v3 should polyfill this.
import 'promise.withresolvers/auto';
/* application specific logic */
// Re-export jQuery
// FIXME: Remove this requirement from torture tests.

View File

@@ -155,6 +155,7 @@ import {
NOTIFICATION_TIMEOUT_TYPE
} from './react/features/notifications/constants';
import { isModerationNotificationDisplayed } from './react/features/notifications/functions';
import { mediaPermissionPromptVisibilityChanged } from './react/features/overlay/actions';
import { suspendDetected } from './react/features/power-monitor/actions';
import { initPrejoin, isPrejoinPageVisible } from './react/features/prejoin/functions';
import { disableReceiver, stopReceiver } from './react/features/remote-control/actions';
@@ -434,6 +435,15 @@ export default {
requestedVideo = true;
}
if (!config.disableInitialGUM) {
JitsiMeetJS.mediaDevices.addEventListener(
JitsiMediaDevicesEvents.PERMISSION_PROMPT_IS_SHOWN,
browserName =>
APP.store.dispatch(
mediaPermissionPromptVisibilityChanged(true, browserName))
);
}
let tryCreateLocalTracks = Promise.resolve([]);
// On Electron there is no permission prompt for granting permissions. That's why we don't need to
@@ -442,7 +452,8 @@ export default {
const timeout = browser.isElectron() ? 15000 : 60000;
const audioOptions = {
devices: [ MEDIA_TYPE.AUDIO ],
timeout
timeout,
firePermissionPromptIsShownEvent: true
};
// Spot uses the _desktopSharingSourceDevice config option to use an external video input device label as
@@ -477,7 +488,8 @@ export default {
} else if (requestedAudio || requestedVideo) {
tryCreateLocalTracks = APP.store.dispatch(createInitialAVTracks({
devices: initialDevices,
timeout
timeout,
firePermissionPromptIsShownEvent: true
}, recordTimeMetrics)).then(({ tracks, errors: pErrors }) => {
Object.assign(errors, pErrors);
@@ -485,6 +497,15 @@ export default {
});
}
// Hide the permissions prompt/overlay as soon as the tracks are created. Don't wait for the connection to
// be established, as in some cases like when auth is required, connection won't be established until the user
// inputs their credentials, but the dialog would be overshadowed by the overlay.
tryCreateLocalTracks.then(tracks => {
APP.store.dispatch(mediaPermissionPromptVisibilityChanged(false));
return tracks;
});
return {
tryCreateLocalTracks,
errors
@@ -1012,7 +1033,7 @@ export default {
},
/**
* Will be filled with values only when config.testing.testMode is true.
* Will be filled with values only when config.debug is enabled.
* Its used by torture to check audio levels.
*/
audioLevelsMap: {},
@@ -1399,19 +1420,28 @@ export default {
/**
* Creates desktop (screensharing) {@link JitsiLocalTrack}
*
* @param {Object} [options] - Screen sharing options that will be passed to
* createLocalTracks.
* @param {Object} [options.desktopSharing]
* @param {Object} [options.desktopStream] - An existing desktop stream to
* use instead of creating a new desktop stream.
* @return {Promise.<JitsiLocalTrack>} - A Promise resolved with
* {@link JitsiLocalTrack} for the screensharing or rejected with
* {@link JitsiTrackError}.
*
* @private
*/
_createDesktopTrack() {
_createDesktopTrack(options = {}) {
const didHaveVideo = !this.isLocalVideoMuted();
const getDesktopStreamPromise = createLocalTracksF({
desktopSharingSourceDevice: config._desktopSharingSourceDevice,
devices: [ 'desktop' ]
});
const getDesktopStreamPromise = options.desktopStream
? Promise.resolve([ options.desktopStream ])
: createLocalTracksF({
desktopSharingSourceDevice: options.desktopSharingSources
? null : config._desktopSharingSourceDevice,
desktopSharingSources: options.desktopSharingSources,
devices: [ 'desktop' ]
});
return getDesktopStreamPromise.then(desktopStreams => {
// Stores the "untoggle" handler which remembers whether was
@@ -1588,9 +1618,9 @@ export default {
newLvl = 0;
}
if (config.testing?.testMode) {
if (config.debug) {
this.audioLevelsMap[id] = newLvl;
if (config.testing?.debugAudioLevels) {
if (config.debugAudioLevels) {
logger.log(`AudioLevel:${id}/${newLvl}`);
}
}

124
config.js
View File

@@ -84,9 +84,6 @@ var config = {
// Allows the setting of a custom bandwidth value from the UI.
// assumeBandwidth: true,
// Enables use of getDisplayMedia in electron
// electronUseGetDisplayMedia: false,
// Enables the use of the codec selection API supported by the browsers .
// enableCodecSelectionAPI: false,
@@ -106,12 +103,6 @@ var config = {
// Dump transcripts to a <transcript> element for debugging.
// dumpTranscript: false,
// Log the audio levels.
// debugAudioLevels: true,
// Will replace ice candidates IPs with invalid ones in order to fail ice.
// failICE: true,
},
// Disables moderator indicators.
@@ -371,6 +362,9 @@ var config = {
// Recording
// DEPRECATED. Use recordingService.enabled instead.
// fileRecordingsEnabled: false,
// Enable the dropbox integration.
// dropbox: {
// appKey: '<APP_KEY>', // Specify your app key here.
@@ -465,7 +459,7 @@ var config = {
// // Translation languages.
// // Available languages can be found in
// // ./lang/translation-languages.json.
// // ./src/react/features/transcribing/translation-languages.json.
// translationLanguages: ['en', 'es', 'fr', 'ro'],
// // Important languages to show on the top of the language list.
@@ -576,6 +570,30 @@ var config = {
// useKSVC: true
// },
//
// DEPRECATED! Use `codec specific settings` instead.
// // Provides a way to configure the maximum bitrates that will be enforced on the simulcast streams for
// // video tracks. The keys in the object represent the type of the stream (LD, SD or HD) and the values
// // are the max.bitrates to be set on that particular type of stream. The actual send may vary based on
// // the available bandwidth calculated by the browser, but it will be capped by the values specified here.
// // This is currently not implemented on app based clients on mobile.
// maxBitratesVideo: {
// H264: {
// low: 200000,
// standard: 500000,
// high: 1500000,
// },
// VP8 : {
// low: 200000,
// standard: 500000,
// high: 1500000,
// },
// VP9: {
// low: 100000,
// standard: 300000,
// high: 1200000,
// },
// },
//
// // The options can be used to override default thresholds of video thumbnail heights corresponding to
// // the video quality levels used in the application. At the time of this writing the allowed levels are:
// // 'low' - for the low quality level (180p at the time of this writing)
@@ -593,6 +611,22 @@ var config = {
//
// // Provides a way to set the codec preference on mobile devices, both on RN and mobile browser based endpoint
// mobileCodecPreferenceOrder: [ 'VP8', 'VP9', 'H264' ],
//
// // DEPRECATED! Use `codecPreferenceOrder/mobileCodecPreferenceOrder` instead.
// // Provides a way to prevent a video codec from being negotiated on the JVB connection. The codec specified
// // here will be removed from the list of codecs present in the SDP answer generated by the client. If the
// // same codec is specified for both the disabled and preferred option, the disable settings will prevail.
// // Note that 'VP8' cannot be disabled since it's a mandatory codec, the setting will be ignored in this case.
// disabledCodec: 'H264',
//
// // DEPRECATED! Use `codecPreferenceOrder/mobileCodecPreferenceOrder` instead.
// // Provides a way to set a preferred video codec for the JVB connection. If 'H264' is specified here,
// // simulcast will be automatically disabled since JVB doesn't support H264 simulcast yet. This will only
// // rearrange the the preference order of the codecs in the SDP answer generated by the browser only if the
// // preferred codec specified here is present. Please ensure that the JVB offers the specified codec for this
// // to take effect.
// preferredCodec: 'VP8',
//
// },
// Notification timeouts
@@ -751,9 +785,6 @@ var config = {
// and microsoftApiApplicationClientID
// enableCalendarIntegration: false,
// The client id for the google APIs used for the calendar integration, youtube livestreaming, etc.
// googleApiApplicationClientID: '<client_id>',
// Configs for prejoin page.
// prejoinConfig: {
// // When 'true', it shows an intermediate page before joining, where the user can configure their devices.
@@ -788,6 +819,10 @@ var config = {
// set or the lobby is not enabled.
// enableInsecureRoomNameWarning: false,
// Whether to automatically copy invitation URL after creating a room.
// Document should be focused for this option to work
// enableAutomaticUrlCopy: false,
// Array with avatar URL prefixes that need to use CORS.
// corsAvatarURLs: [ 'https://www.gravatar.com/avatar/' ],
@@ -872,7 +907,7 @@ var config = {
// Overrides the buttons displayed in the main toolbar. Depending on the screen size the number of displayed
// buttons varies from 2 buttons to 8 buttons. Every array in the mainToolbarButtons array will replace the
// corresponding default buttons configuration matched by the number of buttons specified in the array. Arrays with
// more than 8 buttons or less then 2 buttons will be ignored. When there there isn't an override for a certain
// more than 8 buttons or less then 2 buttons will be ignored. When there there isn't an override for a cerain
// configuration (for example when 3 buttons are displayed) the default jitsi-meet configuration will be used.
// The order of the buttons in the array is preserved.
// mainToolbarButtons: [
@@ -1085,6 +1120,15 @@ var config = {
// { urls: 'stun:jitsi-meet.example.com:3478' },
{ urls: 'stun:meet-jit-si-turnrelay.jitsi.net:443' },
],
// DEPRECATED! Use `codecPreferenceOrder/mobileCodecPreferenceOrder` instead.
// Provides a way to set the video codec preference on the p2p connection. Acceptable
// codec values are 'VP8', 'VP9' and 'H264'.
// preferredCodec: 'H264',
// DEPRECATED! Use `codecPreferenceOrder/mobileCodecPreferenceOrder` instead.
// Provides a way to prevent a video codec from being negotiated on the p2p connection.
// disabledCodec: '',
},
analytics: {
@@ -1585,38 +1629,20 @@ var config = {
// video: true
// },
// },
// The default type of desktop sharing sources that will be used in the electron app.
// desktopSharingSources: ['screen', 'window'],
// Disables the echo cancelation for local audio tracks.
// disableAEC: true,
// Disables the auto gain control for local audio tracks.
// disableAGC: true,
// Disables the audio processing (echo cancelation, auto gain control and noise suppression) for local audio tracks.
// disableAP: true,
// Disables the anoise suppression for local audio tracks.
// disableNS: true,
// Replaces the display name with the JID of the participants.
// displayJids: true,
// Enables disables talk while muted detection.
// enableTalkWhileMuted: true,
// Sets the peer connection ICE transport policy to "relay".
// forceTurnRelay: true,
// List of undocumented settings used in jitsi-meet
/**
_immediateReloadThreshold
debug
debugAudioLevels
deploymentInfo
dialOutAuthUrl
dialOutCodesUrl
dialOutRegionUrl
disableRemoteControl
displayJids
firefox_fake_device
googleApiApplicationClientID
iAmRecorder
iAmSipGateway
microsoftApiApplicationClientID
@@ -1635,7 +1661,14 @@ var config = {
_peerConnStatusRtcMuteTimeout
avgRtpStatsN
desktopSharingSources
disableAEC
disableAGC
disableAP
disableHPF
disableLocalStats
disableNS
enableTalkWhileMuted
forceTurnRelay
hiddenDomain
hiddenFromRecorderFeatureEnabled
ignoreStartMuted
@@ -1712,7 +1745,7 @@ var config = {
// 'notify.participantsWantToJoin', // shown when lobby is enabled and participants request to join meeting
// 'notify.passwordRemovedRemotely', // shown when a password has been removed remotely
// 'notify.passwordSetRemotely', // shown when a password has been set remotely
// 'notify.raisedHand', // shown when a participant used raise hand,
// 'notify.raisedHand', // shown when a partcipant used raise hand,
// 'notify.screenShareNoAudio', // shown when the audio could not be shared for the selected screen
// 'notify.screenSharingAudioOnlyTitle', // shown when the best performance has been affected by screen sharing
// 'notify.selfViewTitle', // show "You can always un-hide the self-view from settings"
@@ -1743,7 +1776,7 @@ var config = {
// disableFilmstripAutohiding: false,
// filmstrip: {
// // Disable the vertical/horizontal filmstrip.
// // Disable the vertical/horizonal filmstrip.
// disabled: false,
// // Disables user resizable filmstrip. Also, allows configuration of the filmstrip
// // (width, tiles aspect ratios) through the interfaceConfig options.
@@ -1802,10 +1835,9 @@ var config = {
// //disableLogCollector: true,
// // Individual loggers are customizable.
// loggers: {
// // The following are too verbose in their logging with the default level.
// 'modules/RTC/TraceablePeerConnection.js': 'info',
// 'modules/xmpp/strophe.util.js': 'log',
// },
// // The following are too verbose in their logging with the default level.
// 'modules/RTC/TraceablePeerConnection.js': 'info',
// 'modules/xmpp/strophe.util.js': 'log',
// },
// Application logo url
@@ -1863,6 +1895,12 @@ var config = {
// hideLoginButton: true,
};
// Temporary backwards compatibility with old mobile clients.
config.flags = config.flags || {};
config.flags.sourceNameSignaling = true;
config.flags.sendMultipleVideoStreams = true;
config.flags.receiveMultipleVideoStreams = true;
// Set the default values for JaaS customers
if (enableJaaS) {
config.dialInNumbersUrl = 'https://conference-mapper.jitsi.net/v1/access/dids';

View File

@@ -19,11 +19,11 @@
flex-direction: column;
.description {
color: #2f3237;
font-size: 14px;
line-height: 18px;
margin-bottom: 16px;
max-width: 436px;
color: #2f3237;
font-size: 14px;
line-height: 18px;
margin-bottom: 16px;
max-width: 436px;
}
}
@@ -127,8 +127,7 @@
cursor: pointer;
}
&.with-click-handler:hover,
&.with-click-handler:focus {
&.with-click-handler:hover {
background-color: #c7ddff;
}
@@ -156,22 +155,14 @@
margin-right: 16px;
position: absolute;
&>svg {
&> svg {
fill: #0074e0;
}
}
.item:hover,
.item:focus,
.item:focus-within {
.item:hover, .item:focus, .item:focus-within {
.delete-meeting {
display: block;
}
.delete-meeting:hover {
&>svg {
fill: #4687ED;
}
}
}
}
}

View File

@@ -175,6 +175,22 @@ case "$1" in
fi
fi
# Fixes multi-stream flags to workaround problem with mobile joining a multi-stream call with multi-stream disabled
FIX_MSG="// Temporary backwards compatibility with old mobile clients."
if ! grep -q "^${FIX_MSG}" $JITSI_MEET_CONFIG; then
echo $FIX_MSG >> $JITSI_MEET_CONFIG
echo "config.flags = config.flags || {};" >> $JITSI_MEET_CONFIG
fi
if ! grep -q "^config.flags.sourceNameSignaling*" $JITSI_MEET_CONFIG; then
echo "config.flags.sourceNameSignaling = true;" >> $JITSI_MEET_CONFIG
fi
if ! grep -q "^config.flags.sendMultipleVideoStreams*" $JITSI_MEET_CONFIG; then
echo "config.flags.sendMultipleVideoStreams = true;" >> $JITSI_MEET_CONFIG
fi
if ! grep -q "^config.flags.receiveMultipleVideoStreams*" $JITSI_MEET_CONFIG; then
echo "config.flags.receiveMultipleVideoStreams = true;" >> $JITSI_MEET_CONFIG
fi
if [[ "$FORCE_OPENRESTY" = "true" ]]; then
NGX_COMMON_CONF_PATH="/usr/local/openresty/nginx/conf/$JVB_HOSTNAME.conf"
NGX_SVC_NAME=openresty

1
globals.d.ts vendored
View File

@@ -18,6 +18,7 @@ declare global {
JITSI_MEET_LITE_SDK?: boolean;
interfaceConfig?: any;
JitsiMeetJS?: any;
JitsiMeetElectron?: any;
PressureObserver?: any;
PressureRecord?: any;
ReactNativeWebView?: any;

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.7 KiB

After

Width:  |  Height:  |  Size: 4.2 KiB

View File

@@ -41,6 +41,14 @@ var interfaceConfig = {
*/
DISABLE_PRESENCE_STATUS: false,
/**
* Whether the ringing sound in the call/ring overlay is disabled. If
* {@code undefined}, defaults to {@code false}.
*
* @type {boolean}
*/
DISABLE_RINGING: false,
/**
* Whether the speech to text transcription subtitles panel is disabled.
* If {@code undefined}, defaults to {@code false}.
@@ -62,6 +70,9 @@ var interfaceConfig = {
ENABLE_DIAL_OUT: true,
// DEPRECATED. Animation no longer supported.
// ENABLE_FEEDBACK_ANIMATION: false,
FILM_STRIP_MAX_HEIGHT: 120,
GENERATE_ROOMNAMES_ON_WELCOME_PAGE: true,
@@ -216,7 +227,7 @@ var interfaceConfig = {
/**
* Specify custom URL for downloading f droid app.
*/
// MOBILE_DOWNLOAD_LINK_F_DROID: 'https://f-droid.org/packages/org.jitsi.meet/',
// MOBILE_DOWNLOAD_LINK_F_DROID: 'https://f-droid.org/en/packages/org.jitsi.meet/',
// Connection indicators (
// CONNECTION_INDICATOR_AUTO_HIDE_ENABLED,

View File

@@ -1,6 +1,2 @@
THIS_DIR=$(cd -P "$(dirname "$(readlink "${BASH_SOURCE[0]}" || echo "${BASH_SOURCE[0]}")")" && pwd)
. ${THIS_DIR}/../node_modules/react-native/scripts/find-node-for-xcode.sh
export NODE_BINARY=$(command -v node)
export ENTRY_FILE="${PROJECT_DIR}/../../index.ios.js"

View File

@@ -1726,7 +1726,7 @@ PODS:
- React
- RNCAsyncStorage (1.23.1):
- React-Core
- RNCClipboard (1.14.3):
- RNCClipboard (1.14.1):
- React-Core
- RNDefaultPreference (1.4.4):
- React-Core
@@ -2197,7 +2197,7 @@ SPEC CHECKSUMS:
ReactCommon: 6a952e50c2a4b694731d7682aaa6c79bc156e4ad
RNCalendarEvents: 7e65eb4a94f53c1744d1e275f7fafcfaa619f7a3
RNCAsyncStorage: 826b603ae9c0f88b5ac4e956801f755109fa4d5c
RNCClipboard: 2821ac938ef46f736a8de0c8814845dde2dcbdfb
RNCClipboard: 0a720adef5ec193aa0e3de24c3977222c7e52a37
RNDefaultPreference: 08bdb06cfa9188d5da97d4642dac745218d7fb31
RNDeviceInfo: 02ea8b23e2280fa18e00a06d7e62804d74028579
RNGestureHandler: 939f21fabf5d45a725c0bf175eb819dd25cf2e70
@@ -2211,4 +2211,4 @@ SPEC CHECKSUMS:
PODFILE CHECKSUM: 7ed908101076ca2c595b633c34648bcfc7e98614
COCOAPODS: 1.16.2
COCOAPODS: 1.16.1

View File

@@ -34,10 +34,6 @@
jitsiMeet.universalLinkDomains = @[@"meet.jit.si", @"alpha.jitsi.net", @"beta.meet.jit.si"];
jitsiMeet.defaultConferenceOptions = [JitsiMeetConferenceOptions fromBuilder:^(JitsiMeetConferenceOptionsBuilder *builder) {
// For testing configOverrides a room needs to be set
// builder.room = @"test0988test";
[builder setFeatureFlag:@"welcomepage.enabled" withBoolean:YES];
[builder setFeatureFlag:@"ios.screensharing.enabled" withBoolean:YES];
[builder setFeatureFlag:@"ios.recording.enabled" withBoolean:YES];

View File

@@ -88,8 +88,8 @@
[self _onJitsiMeetViewDelegateEvent:@"CONFERENCE_WILL_JOIN" withData:data];
}
// - (void)customButtonPressed:(NSDictionary *)data {
// [self _onJitsiMeetViewDelegateEvent:@"CUSTOM_BUTTON_PRESSED" withData:data];
// - (void)customOverflowMenuButtonPressed:(NSDictionary *)data {
// [self _onJitsiMeetViewDelegateEvent:@"CUSTOM_OVERFLOW_MENU_BUTTON_PRESSED" withData:data];
// }
#if 0

View File

@@ -69,7 +69,7 @@ platform :ios do
end
)
# Increment the build number by 1
# Inrement the build number by 1
increment_build_number(
build_number: Time.now.to_i,
xcodeproj: "app/app.xcodeproj"

View File

@@ -54,7 +54,7 @@ pushd ${RELEASE_REPO}
# Put the new files in the repo
cp -a ${PROJECT_REPO}/ios/sdk/out/JitsiMeetSDK.xcframework lite/Frameworks/
cp -a ${PROJECT_REPO}/ios/Pods/hermes-engine/destroot/Library/Frameworks/universal/hermes.xcframework lite/Frameworks/
cp -a ${PROJECT_REPO}/ios/sdk/out/hermes.xcframework lite/Frameworks/
# Add all files to git
git add -A .

View File

@@ -54,7 +54,7 @@ pushd ${RELEASE_REPO}
# Put the new files in the repo
cp -a ${PROJECT_REPO}/ios/sdk/out/JitsiMeetSDK.xcframework Frameworks/
cp -a ${PROJECT_REPO}/ios/Pods/hermes-engine/destroot/Library/Frameworks/universal/hermes.xcframework Frameworks/
cp -a ${PROJECT_REPO}/ios/sdk/out/hermes.xcframework Frameworks/
# Add all files to git
git add -A .

View File

@@ -21,7 +21,6 @@
#import <WebRTC/WebRTC.h>
#import "JitsiAudioSession+Private.h"
#import "callkit/JMCallKitProxy.h"
// Audio mode
@@ -55,7 +54,6 @@ static NSString * const kDeviceTypeUnknown = @"UNKNOWN";
RTCAudioSessionConfiguration *audioCallConfig;
RTCAudioSessionConfiguration *videoCallConfig;
RTCAudioSessionConfiguration *earpieceConfig;
BOOL audioDisabled;
BOOL forceSpeaker;
BOOL forceEarpiece;
BOOL isSpeakerOn;
@@ -148,36 +146,9 @@ RCT_EXPORT_MODULE();
#pragma mark - Exported methods
RCT_EXPORT_METHOD(setDisabled:(BOOL)disabled
resolve:(RCTPromiseResolveBlock)resolve
reject:(RCTPromiseRejectBlock)reject) {
if (audioDisabled == disabled) {
resolve(nil);
return;
}
RCTLogInfo(@"[AudioMode] audio disabled: %d", disabled);
audioDisabled = disabled;
JMCallKitProxy.enabled = !disabled;
RTCAudioSession *session = JitsiAudioSession.rtcAudioSession;
if (disabled) {
[session removeDelegate:self];
} else {
[session addDelegate:self];
}
session.useManualAudio = disabled;
}
RCT_EXPORT_METHOD(setMode:(int)mode
resolve:(RCTPromiseResolveBlock)resolve
reject:(RCTPromiseRejectBlock)reject) {
if (audioDisabled) {
resolve(nil);
return;
}
RTCAudioSessionConfiguration *config = [self configForMode:mode];
NSError *error;
@@ -206,11 +177,6 @@ RCT_EXPORT_METHOD(setMode:(int)mode
RCT_EXPORT_METHOD(setAudioDevice:(NSString *)device
resolve:(RCTPromiseResolveBlock)resolve
reject:(RCTPromiseRejectBlock)reject) {
if (audioDisabled) {
resolve(nil);
return;
}
RCTLogInfo(@"[AudioMode] Selected device: %@", device);
RTCAudioSession *session = JitsiAudioSession.rtcAudioSession;
@@ -273,10 +239,6 @@ RCT_EXPORT_METHOD(setAudioDevice:(NSString *)device
}
RCT_EXPORT_METHOD(updateDeviceList) {
if (audioDisabled) {
return;
}
[self notifyDevicesChanged];
}

View File

@@ -24,15 +24,11 @@
}
+ (void)activateWithAudioSession:(AVAudioSession *)session {
if (!self.rtcAudioSession.useManualAudio) {
[self.rtcAudioSession audioSessionDidActivate:session];
}
[self.rtcAudioSession audioSessionDidActivate:session];
}
+ (void)deactivateWithAudioSession:(AVAudioSession *)session {
if (!self.rtcAudioSession.useManualAudio) {
[self.rtcAudioSession audioSessionDidDeactivate:session];
}
[self.rtcAudioSession audioSessionDidDeactivate:session];
}
@end

View File

@@ -231,9 +231,6 @@
}
- (void)setDefaultConferenceOptions:(JitsiMeetConferenceOptions *)defaultConferenceOptions {
// For testing configOverrides a room needs to be set,
// thus the following check needs to be commented out
if (defaultConferenceOptions != nil && defaultConferenceOptions.room != nil) {
@throw [NSException exceptionWithName:@"RuntimeError"
reason:@"'room' must be null in the default conference options"

View File

@@ -128,6 +128,6 @@
*
* The `data` dictionary contains a `id`, `text` key.
*/
- (void)customButtonPressed:(NSDictionary *)data;
- (void)customOverflowMenuButtonPressed:(NSDictionary *)data;
@end

File diff suppressed because it is too large Load Diff

View File

@@ -334,7 +334,6 @@
"kickParticipantButton": "Expulser",
"kickParticipantDialog": "Êtes-vous sûr(e) de vouloir expulser ce participant ?",
"kickParticipantTitle": "Expulser ce participant ?",
"kickSystemTitle": "Oups ! Vous avez été expulsé de la réunion",
"kickTitle": "Oups ! vous avez été expulsé(e) par {{participantDisplayName}}",
"linkMeeting": "Relier la conférence",
"linkMeetingTitle": "Relier la conférence à Salesforce",
@@ -440,10 +439,7 @@
"shareScreenWarningD2": "vous devez arrêter le partage d'audio, démarrer le partage d'écran et cocher l'option \"Partager l'audio\".",
"shareScreenWarningH1": "Si vous voulez partager uniquement votre écran:",
"shareScreenWarningTitle": "Vous devez cesser de partager votre audio avant de partager votre écran",
"shareVideoConfirmPlay": "Vous êtes sur le point d'ouvrir un site web externe. Voulez-vous continuer ?",
"shareVideoConfirmPlayTitle": "{{name}} a partagé une vidéo avec vous.",
"shareVideoLinkError": "Veuillez renseigner un lien de diffusion vidéo fonctionnel.",
"shareVideoLinkStopped": "La vidéo de {{name}} a été arrêtée.",
"shareVideoTitle": "Partager une vidéo",
"shareYourScreen": "Partager votre écran",
"shareYourScreenDisabled": "Le partage d'écran est désactivé.",
@@ -642,7 +638,6 @@
"on": "En direct",
"onBy": "{{name}} a démarré la diffusion en direct",
"pending": "Lancement du direct…",
"policyError": "Vous avez essayé de démarrer une diffusion en direct trop rapidement. Veuillez réessayer plus tard !",
"serviceName": "Service de diffusion en direct",
"sessionAlreadyActive": "Cette session est déjà en cours d'enregistrement ou de diffusion.",
"signIn": "Se connecter avec Google",
@@ -741,7 +736,6 @@
"connectedOneMember": "{{name}} a rejoint la réunion",
"connectedThreePlusMembers": "{{name}} et {{count}} autres personnes ont rejoint la réunion",
"connectedTwoMembers": "{{first}} et {{second}} ont rejoint la réunion",
"connectionFailed": "Connexion échouée. Veuillez réessayer plus tard !",
"dataChannelClosed": "Qualité vidéo dégradée",
"dataChannelClosedDescription": "Le canal de communication avec le Bridge a été interrompu, la qualité vidéo se trouve limitée à sa valeur la plus faible.",
"dataChannelClosedDescriptionWithAudio": "Le canal de pont est fermé, ce qui peut entraîner des perturbations de l'audio et de la vidéo.",
@@ -756,9 +750,6 @@
"gifsMenu": "GIPHY",
"groupTitle": "Notifications",
"hostAskedUnmute": "Le modérateur souhaite vous donner la parole",
"invalidTenant": "Tenant invalide",
"invalidTenantHyphenDescription": "Le tenant que vous utilisez est invalide (commence ou se termine par '-').",
"invalidTenantLengthDescription": "Le tenant que vous utilisez est trop long.",
"invitedOneMember": "{{name}} a été invité(e)",
"invitedThreePlusMembers": "{{name}} et {{count}} autres ont été invités",
"invitedTwoMembers": "{{first}} et {{second}} ont été invités",
@@ -795,7 +786,6 @@
"newDeviceAction": "Utiliser",
"newDeviceAudioTitle": "Nouveau périphérique audio détecté",
"newDeviceCameraTitle": "Nouvelle caméra détectée",
"nextToSpeak": "Vous êtes le prochain à prendre la parole",
"noiseSuppressionDesktopAudioDescription": "La suppression de bruit ne peut pas être activée en même temps que la partage audio du système, veuillez le désactiver et réessayer.",
"noiseSuppressionFailedTitle": "Échec du démarrage de la suppression de bruit",
"noiseSuppressionStereoDescription": "La suppression de bruit dune source stéréo nest pas encore supportée.",
@@ -830,7 +820,6 @@
"videoUnmuteBlockedDescription": "Le rétablissement de la vidéo a été bloqué temporairement en raison de limites système.",
"videoUnmuteBlockedTitle": "Rétablissement de la caméra bloqué !",
"viewLobby": "Voir la salle d'attente",
"viewParticipants": "Voir les participants",
"viewVisitors": "Voir les visiteurs",
"waitingParticipants": "{{waitingParticipants}} personnes",
"waitingVisitors": "Visiteurs en attente dans la file : {{waitingVisitors}}",
@@ -850,8 +839,6 @@
"breakoutRooms": "Salles annexes",
"goLive": "Passer en direct",
"invite": "Inviter quelqu'un",
"lowerAllHands": "Abaisser toutes les mains",
"lowerHand": "Abaisser la main",
"moreModerationActions": "Options de modération supplémentaires",
"moreModerationControls": "Options de modération supplémentaires",
"moreParticipantOptions": "Options supplémentaires pour les participants",
@@ -888,7 +875,6 @@
"submit": "Envoyer"
},
"by": "Par {{ name }}",
"closeButton": "Fermer le sondage",
"create": {
"addOption": "Ajouter une option",
"answerPlaceholder": "Option {{index}}",
@@ -928,11 +914,9 @@
"configuringDevices": "Configuration des appareils…",
"connectedWithAudioQ": "Êtes-vous connecté avec le microphone ?",
"connection": {
"failed": "Le test de connexion a échoué !",
"good": "Votre connexion Internet est bonne !",
"nonOptimal": "Votre connexion n'est pas optimale",
"poor": "Vous avez une mauvaise connexion",
"running": "Exécution du test de connexion…"
"poor": "Vous avez une mauvaise connexion"
},
"connectionDetails": {
"audioClipping": "Attendez vous à ce que votre son soit coupé.",
@@ -941,7 +925,6 @@
"goodQuality": "Impressionnant ! La qualité de vos médias sera excellente",
"noMediaConnectivity": "Nous n'avons pas pu trouver un moyen d'établir une connectivité multimédia pour ce test. Cela est généralement causé par un pare-feu ou un NAT.",
"noVideo": "Attendez vous à ce que votre qualité vidéo soit très mauvaise.",
"testFailed": "Le test de connexion a rencontré des problèmes inattendus, mais cela pourrait ne pas affecter votre expérience.",
"undetectable": "Si vous ne parvenez toujours pas à passer des appels dans le navigateur, nous vous recommandons de vous assurer que vos haut-parleurs, microphone et caméra sont correctement configurés, que vous avez accordé à votre navigateur les droits d'utiliser votre microphone et votre caméra et que la version de votre navigateur est à jour. Si vous rencontrez toujours des difficultés pour appeler, vous devez contacter le développeur de l'application Web.",
"veryPoorConnection": "Attendez vous à ce que la qualité de votre appel soit très mauvaise",
"videoFreezing": "Attendez vous à ce que votre vidéo saute, soit noire, et pixelisée.",
@@ -1057,7 +1040,6 @@
"onBy": "{{name}} a démarré l'enregistrement",
"onlyRecordSelf": "Enregistrer seulement mon audio et ma vidéo.",
"pending": "Préparation de l'enregistrement de la réunion…",
"policyError": "Vous avez essayé de démarrer un enregistrement trop rapidement. Veuillez réessayer plus tard !",
"recordAudioAndVideo": "Enregistrer l'audio et la vidéo",
"recordTranscription": "Enregistrer la transcription",
"saveLocalRecording": "Sauvegarder lenregistrement local (Beta)",
@@ -1106,7 +1088,6 @@
"desktopShareWarning": "Vous devez repartager l'écran pour que ces paramètres soient utilisés.",
"devices": "Périphériques",
"followMe": "Tout le monde me suit",
"followMeRecorder": "L'enregistreur me suit",
"framesPerSecond": "images par seconde",
"incomingMessage": "un message arrive",
"language": "Langue",
@@ -1253,7 +1234,6 @@
"lobbyButton": "Activer / Désactiver le mode salle d'attente",
"localRecording": "Activer / Désactiver les contrôles d'enregistrement local",
"lockRoom": "Activer / Désactiver le mot de passe de la réunion",
"love": "Cœur",
"lowerHand": "Baisser la main",
"moreActions": "Activer / Désactiver le menu d'actions supplémentaires",
"moreActionsMenu": "Menu d'actions supplémentaires",
@@ -1271,7 +1251,6 @@
"privateMessage": "Envoyer un message privé",
"profile": "Éditer votre profil",
"raiseHand": "Lever la main",
"react": "Réactions aux messages",
"reactions": "Réactions",
"reactionsMenu": "Ouvrir / fermer le menu réactions",
"recording": "Activer / Désactiver l'enregistrement",
@@ -1343,7 +1322,6 @@
"lobbyButtonEnable": "Activer le mode salle d'attente / contrôle des participant(e)s",
"login": "Connexion",
"logout": "Déconnexion",
"love": "Cœur",
"lowerYourHand": "Baisser la main",
"moreActions": "Plus d'actions",
"moreOptions": "Plus d'options",
@@ -1369,7 +1347,6 @@
"raiseYourHand": "Lever la main",
"reactionBoo": "Envoyer réaction huer",
"reactionClap": "Envoyer réaction applaudir",
"reactionHeart": "Envoyer une réaction en forme de cœur",
"reactionLaugh": "Envoyer réaction rire",
"reactionLike": "Envoyer réaction approuver",
"reactionSilence": "Envoyer réaction silence",
@@ -1403,7 +1380,7 @@
"transcribing": {
"ccButtonTooltip": "Activer / Désactiver les sous-titres",
"expandedLabel": "La transcription est actuellement activée",
"failed": "La transcription a échoué",
"failedToStart": "Échec de démarrage de la transcription",
"labelToolTip": "La transcription de la réunion est en cours",
"sourceLanguageDesc": "Actuellement, la langue de la réunion est sélectionnée à <b>{{sourceLanguage}}</b>. <br/> Vous pouvez la changer à partir de ",
"sourceLanguageHere": "ici",
@@ -1504,15 +1481,10 @@
},
"visitors": {
"chatIndicator": "(visiteur)",
"joinMeeting": {
"description": "Vous êtes actuellement un observateur dans cette conférence.",
"raiseHand": "Levez la main",
"title": "Rejoindre la réunion",
"wishToSpeak": "Si vous souhaitez prendre la parole, veuillez lever la main ci-dessous et attendre l'approbation du modérateur."
},
"labelTooltip": "Nombre de Visiteurs: {{count}}",
"labelTooltip": "Nombre de Visiteurs",
"notification": {
"demoteDescription": "Envoyé ici par {{actor}}, levez la main pour participer",
"description": "Pour participer lever la main.",
"noMainParticipantsDescription": "Un participant doit démarrer la réunion. Veuillez réessayer dans un moment.",
"noMainParticipantsTitle": "Cette réunion n'a pas encore commencé.",
"noVisitorLobby": "Vous ne pouvez pas rejoindre tant qu'une salle d'attente est activée pour la réunion.",

View File

@@ -334,7 +334,6 @@
"kickParticipantButton": "Izraidīt",
"kickParticipantDialog": "Vai esat pārliecināti, ka vēlaties izraidīt šo dalībnieku?",
"kickParticipantTitle": "Izraidīt šo dalībnieku?",
"kickSystemTitle": "Ak! Jūs izraidīja no sapulces",
"kickTitle": "Ak! {{participantDisplayName}} izraidīja jūs no sapulces",
"linkMeeting": "Sasaistīt sapulci",
"linkMeetingTitle": "Sasaistīt sapulci ar Salesforce",

File diff suppressed because it is too large Load Diff

View File

@@ -410,7 +410,6 @@
"sendPrivateMessageTitle": "私信回复?",
"serviceUnavailable": "服务不可用",
"sessTerminated": "通话已结束",
"sessTerminatedReason": "会议已经结束",
"sessionRestarted": "由于连接问题,呼叫重新启动。",
"shareAudio": "继续",
"shareAudioTitle": "如何分享音频",

View File

@@ -307,8 +307,8 @@
"contactSupport": "Contact support",
"copied": "Copied",
"copy": "Copy",
"demoteParticipantDialog": "Are you sure you want to move this participant to viewer?",
"demoteParticipantTitle": "Move to viewer",
"demoteParticipantDialog": "Are you sure you want to move this participant to visitor?",
"demoteParticipantTitle": "Move to visitor",
"dismiss": "Dismiss",
"displayNameRequired": "Hi! Whats your name?",
"done": "Done",
@@ -831,9 +831,9 @@
"videoUnmuteBlockedTitle": "Camera unmute and desktop sharing blocked!",
"viewLobby": "View lobby",
"viewParticipants": "View participants",
"viewVisitors": "View viewers",
"viewVisitors": "View visitors",
"waitingParticipants": "{{waitingParticipants}} people",
"waitingVisitors": "Viewers waiting in queue: {{waitingVisitors}}",
"waitingVisitors": "Visitors waiting in queue: {{waitingVisitors}}",
"waitingVisitorsTitle": "The meeting is not live yet!",
"whiteboardLimitDescription": "Please save your progress, as the user limit will soon be reached and the whiteboard will close.",
"whiteboardLimitTitle": "Whiteboard usage"
@@ -870,7 +870,7 @@
"participantsList": "Meeting participants ({{count}})",
"visitorInQueue": " (waiting {{count}})",
"visitorRequests": " (requests {{count}})",
"visitors": "Viewers {{count}}",
"visitors": "Visitors {{count}}",
"waitingLobby": "Waiting in lobby ({{count}})"
},
"search": "Search participants",
@@ -1083,7 +1083,7 @@
"about": "You can add a $t(lockRoomPassword) to your meeting. Participants will need to provide the $t(lockRoomPassword) before they are allowed to join the meeting.",
"aboutReadOnly": "Moderator participants can add a $t(lockRoomPassword) to the meeting. Participants will need to provide the $t(lockRoomPassword) before they are allowed to join the meeting.",
"insecureRoomNameWarningNative": "The room name is unsafe. Unwanted participants may join your meeting. {{recommendAction}} Learn more about securing your meeting ",
"insecureRoomNameWarningWeb": "The room name is unsafe. Unwanted participants may join your meeting. {{recommendAction}} Learn more about securing your meeting <a href=\"{{securityUrl}}\" rel=\"security\" target=\"_blank\">here</a>.",
"insecureRoomNameWarningWeb": "The room name is unsafe. Unwanted participants may join your meeting. {{recommendAction}} Learn more about securing you meeting <a href=\"{{securityUrl}}\" rel=\"security\" target=\"_blank\">here</a>.",
"title": "Security Options",
"unsafeRoomActions": {
"meeting": "Consider securing your meeting using the security button.",
@@ -1183,7 +1183,6 @@
"fearful": "Fearful",
"happy": "Happy",
"hours": "{{count}}h",
"labelTooltip": "Number of participants: {{count}}",
"minutes": "{{count}}m",
"name": "Name",
"neutral": "Neutral",
@@ -1452,7 +1451,7 @@
},
"videothumbnail": {
"connectionInfo": "Connection Info",
"demote": "Move to viewer",
"demote": "Move to visitor",
"domute": "Mute",
"domuteOthers": "Mute everyone else",
"domuteVideo": "Disable camera",
@@ -1504,21 +1503,21 @@
"webAssemblyWarningDescription": "WebAssembly disabled or not supported by this browser"
},
"visitors": {
"chatIndicator": "(viewer)",
"chatIndicator": "(visitor)",
"joinMeeting": {
"description": "You're currently a viewer in this conference.",
"description": "You're currently an observer in this conference.",
"raiseHand": "Raise your hand",
"title": "Joining meeting",
"wishToSpeak": "If you wish to speak, please raise your hand below and wait for the moderator's approval."
},
"labelTooltip": "Number of viewers: {{count}}",
"labelTooltip": "Number of visitors: {{count}}",
"notification": {
"demoteDescription": "Sent here by {{actor}}, raise your hand to participate",
"noMainParticipantsDescription": "A participant needs to start the meeting. Please try again in a bit.",
"noMainParticipantsTitle": "This meeting hasnt started yet.",
"noVisitorLobby": "You cannot join while there is a lobby enabled for the meeting.",
"notAllowedPromotion": "A participant needs to allow your request first.",
"title": "You are a viewer in the meeting"
"title": "You are a visitor in the meeting"
},
"waitingMessage": "You'll join the meeting as soon as it is live!"
},

View File

@@ -590,7 +590,7 @@ function initCommands() {
* @param { string } arg.title - Notification title.
* @param { string } arg.description - Notification description.
* @param { string } arg.uid - Optional unique identifier for the notification.
* @param { string } arg.type - Notification type, either `error`, `normal`, `success` or `warning`.
* @param { string } arg.type - Notification type, either `error`, `info`, `normal`, `success` or `warning`.
* Defaults to "normal" if not provided.
* @param { string } arg.timeout - Timeout type, either `short`, `medium`, `long` or `sticky`.
* Defaults to "short" if not provided.
@@ -851,8 +851,6 @@ function initCommands() {
'overwrite-config': config => {
const whitelistedConfig = getWhitelistedJSON('config', config);
logger.info(`Overwriting config with: ${JSON.stringify(whitelistedConfig)}`);
APP.store.dispatch(overwriteConfig(whitelistedConfig));
},
'toggle-virtual-background': () => {
@@ -1573,8 +1571,8 @@ class API {
formattedArgument += `${arg.toString()}: ${arg.stack}`;
} else if (typeof arg === 'object') {
// NOTE: The non-enumerable properties of the objects wouldn't be included in the string after
// JSON.stringify. For example Map instance will be translated to '{}'. So I think we have to eventually
// do something better for parsing the arguments. But since this option for stringify is part of the
// JSON.strigify. For example Map instance will be translated to '{}'. So I think we have to eventually
// do something better for parsing the arguments. But since this option for strigify is part of the
// public interface and I think it could be useful in some cases I will it for now.
try {
formattedArgument += JSON.stringify(arg);

View File

@@ -194,6 +194,80 @@ function changeParticipantNumber(APIInstance, number) {
APIInstance._numberOfParticipants += number;
}
/**
* Generates the URL for the iframe.
*
* @param {string} domain - The domain name of the server that hosts the
* conference.
* @param {string} [options] - Another optional parameters.
* @param {Object} [options.configOverwrite] - Object containing configuration
* options defined in config.js to be overridden.
* @param {Object} [options.interfaceConfigOverwrite] - Object containing
* configuration options defined in interface_config.js to be overridden.
* @param {string} [options.jwt] - The JWT token if needed by jitsi-meet for
* authentication.
* @param {string} [options.lang] - The meeting's default language.
* @param {string} [options.roomName] - The name of the room to join.
* @returns {string} The URL.
*/
function generateURL(domain, options = {}) {
return urlObjectToString({
...options,
url: `https://${domain}/#jitsi_meet_external_api_id=${id}`
});
}
/**
* Parses the arguments passed to the constructor. If the old format is used
* the function translates the arguments to the new format.
*
* @param {Array} args - The arguments to be parsed.
* @returns {Object} JS object with properties.
*/
function parseArguments(args) {
if (!args.length) {
return {};
}
const firstArg = args[0];
switch (typeof firstArg) {
case 'string': // old arguments format
case 'undefined': {
// Not sure which format but we are trying to parse the old
// format because if the new format is used everything will be undefined
// anyway.
const [
roomName,
width,
height,
parentNode,
configOverwrite,
interfaceConfigOverwrite,
jwt,
onload,
lang
] = args;
return {
roomName,
width,
height,
parentNode,
configOverwrite,
interfaceConfigOverwrite,
jwt,
onload,
lang
};
}
case 'object': // new arguments format
return args[0];
default:
throw new Error('Can\'t parse the arguments!');
}
}
/**
* Compute valid values for height and width. If a number is specified it's
* treated as pixel units. If the value is expressed in px, em, pt or
@@ -262,7 +336,7 @@ export default class JitsiMeetExternalAPI extends EventEmitter {
* @param {string} [options.release] - The key used for specifying release if enabled on the backend.
* @param {string} [options.sandbox] - Sandbox directive for the created iframe, if desired.
*/
constructor(domain, options = {}) {
constructor(domain, ...args) {
super();
const {
roomName = '',
@@ -271,22 +345,21 @@ export default class JitsiMeetExternalAPI extends EventEmitter {
parentNode = document.body,
configOverwrite = {},
interfaceConfigOverwrite = {},
jwt,
lang,
onload,
jwt = undefined,
lang = undefined,
onload = undefined,
invitees,
iceServers,
devices,
userInfo,
e2eeKey,
release,
sandbox
} = options;
sandbox = ''
} = parseArguments(args);
const localStorageContent = jitsiLocalStorage.getItem('jitsiLocalStorage');
this._parentNode = parentNode;
this._url = urlObjectToString({
this._url = generateURL(domain, {
configOverwrite,
iceServers,
interfaceConfigOverwrite,
@@ -298,8 +371,7 @@ export default class JitsiMeetExternalAPI extends EventEmitter {
appData: {
localStorageContent
},
release,
url: `https://${domain}/#jitsi_meet_external_api_id=${id}`
release
});
this._createIFrame(height, width, sandbox);
@@ -1241,10 +1313,6 @@ export default class JitsiMeetExternalAPI extends EventEmitter {
* Returns the state of availability electron share screen via external api.
*
* @returns {Promise}
*
* TODO: should be removed after we make sure that all Electron clients use only versions
* after with the legacy SS suport was removed from the electron SDK. If we remove it now the SS for Electron
* clients with older versions wont work.
*/
_isNewElectronScreensharingSupported() {
return this._transport.sendRequest({

5903
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -26,13 +26,13 @@
"@jitsi/excalidraw": "https://github.com/jitsi/excalidraw/releases/download/v0.0.19/jitsi-excalidraw-0.0.19.tgz",
"@jitsi/js-utils": "2.2.1",
"@jitsi/logger": "2.0.2",
"@jitsi/rnnoise-wasm": "0.2.0",
"@jitsi/rnnoise-wasm": "0.1.0",
"@jitsi/rtcstats": "9.5.1",
"@matrix-org/olm": "https://gitlab.matrix.org/api/v4/projects/27/packages/npm/@matrix-org/olm/-/@matrix-org/olm-3.2.3.tgz",
"@microsoft/microsoft-graph-client": "3.0.1",
"@mui/material": "5.12.1",
"@react-native-async-storage/async-storage": "1.23.1",
"@react-native-clipboard/clipboard": "1.14.3",
"@react-native-clipboard/clipboard": "1.14.1",
"@react-native-community/netinfo": "11.1.0",
"@react-native-community/slider": "4.4.3",
"@react-native-google-signin/google-signin": "10.1.0",
@@ -68,7 +68,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/v1908.0.0+2a5d7fcc/lib-jitsi-meet.tgz",
"lib-jitsi-meet": "https://github.com/jitsi/lib-jitsi-meet#release-8302",
"lodash-es": "4.17.21",
"moment": "2.29.4",
"moment-duration-format": "2.2.2",
@@ -76,7 +76,6 @@
"optional-require": "1.0.3",
"pixelmatch": "5.3.0",
"promise.allsettled": "1.0.4",
"promise.withresolvers": "1.0.3",
"punycode": "2.3.0",
"react": "18.2.0",
"react-dom": "18.2.0",
@@ -135,17 +134,15 @@
"@babel/plugin-transform-private-methods": "7.25.9",
"@babel/preset-env": "7.25.9",
"@babel/preset-react": "7.25.9",
"@jitsi/eslint-config": "5.0.9",
"@jitsi/eslint-config": "4.1.10",
"@react-native/metro-config": "0.75.4",
"@stylistic/eslint-plugin": "2.12.1",
"@types/amplitude-js": "8.16.5",
"@types/audioworklet": "0.0.29",
"@types/dom-screen-wake-lock": "1.0.1",
"@types/jasmine": "5.1.4",
"@types/js-md5": "0.4.3",
"@types/jsonwebtoken": "9.0.7",
"@types/lodash-es": "4.17.12",
"@types/minimatch": "5.1.2",
"@types/mocha": "10.0.10",
"@types/moment-duration-format": "2.2.6",
"@types/offscreencanvas": "2019.7.2",
"@types/pixelmatch": "5.2.5",
@@ -163,25 +160,25 @@
"@types/w3c-image-capture": "1.0.6",
"@types/w3c-web-hid": "1.0.3",
"@types/zxcvbn": "4.4.1",
"@typescript-eslint/eslint-plugin": "8.19.1",
"@typescript-eslint/parser": "8.19.1",
"@wdio/allure-reporter": "9.4.3",
"@wdio/cli": "9.4.3",
"@wdio/globals": "9.4.3",
"@wdio/junit-reporter": "9.4.3",
"@wdio/local-runner": "9.4.3",
"@wdio/mocha-framework": "9.4.3",
"@typescript-eslint/eslint-plugin": "5.59.5",
"@typescript-eslint/parser": "5.59.5",
"@wdio/allure-reporter": "9.2.14",
"@wdio/cli": "9.4.1",
"@wdio/globals": "9.4.1",
"@wdio/jasmine-framework": "9.4.1",
"@wdio/junit-reporter": "9.2.14",
"@wdio/local-runner": "9.4.1",
"babel-loader": "9.1.0",
"babel-plugin-optional-require": "0.3.1",
"circular-dependency-plugin": "5.2.0",
"clean-css-cli": "4.3.0",
"css-loader": "6.8.1",
"eslint": "8.57.0",
"eslint-plugin-import": "2.31.0",
"eslint-plugin-jsdoc": "50.6.1",
"eslint-plugin-react": "7.37.3",
"eslint-plugin-react-native": "5.0.0",
"eslint-plugin-typescript-sort-keys": "3.3.0",
"eslint": "8.40.0",
"eslint-plugin-import": "2.27.5",
"eslint-plugin-jsdoc": "46.2.6",
"eslint-plugin-react": "7.32.2",
"eslint-plugin-react-native": "4.0.0",
"eslint-plugin-typescript-sort-keys": "2.3.0",
"jetifier": "1.6.4",
"jsonwebtoken": "9.0.2",
"metro-react-native-babel-preset": "0.77.0",
@@ -192,9 +189,9 @@
"style-loader": "3.3.1",
"traverse": "0.6.6",
"ts-loader": "9.4.2",
"typescript": "5.7.2",
"typescript": "5.0.4",
"unorm": "1.6.0",
"webdriverio": "9.4.3",
"webdriverio": "9.4.1",
"webpack": "5.95.0",
"webpack-bundle-analyzer": "4.4.2",
"webpack-cli": "5.1.4",
@@ -204,7 +201,7 @@
"@xmldom/xmldom": "0.8.7"
},
"engines": {
"node": ">=22.0.0",
"node": ">=20.0.0",
"npm": ">=10.0.0"
},
"license": "Apache-2.0",
@@ -223,11 +220,9 @@
"tsc-test:native": "tsc --project tsconfig.native.json --listFilesOnly | grep -v node_modules | grep web",
"start": "make dev",
"test": "DOTENV_CONFIG_PATH=tests/.env wdio run tests/wdio.conf.ts",
"test-single": "DOTENV_CONFIG_PATH=tests/.env wdio run tests/wdio.conf.ts --spec",
"test-ff": "DOTENV_CONFIG_PATH=tests/.env wdio run tests/wdio.firefox.conf.ts",
"test-dev": "DOTENV_CONFIG_PATH=tests/.env wdio run tests/wdio.dev.conf.ts",
"test-grid": "DOTENV_CONFIG_PATH=tests/.env wdio run tests/wdio.grid.conf.ts",
"test-grid-single": "DOTENV_CONFIG_PATH=tests/.env wdio run tests/wdio.grid.conf.ts --spec"
"test-grid": "DOTENV_CONFIG_PATH=tests/.env wdio run tests/wdio.grid.conf.ts"
},
"resolutions": {
"@types/react": "17.0.14",

View File

@@ -69,7 +69,7 @@ cd ios && pod install && cd ..
## Android
- In your build.gradle have at least `minSdkVersion = 26`
- In your build.gradle have at least `minSdkVersion = 24`
- In `android/app/src/debug/AndroidManifest.xml` and `android/app/src/main/AndroidManifest.xml`, under the `</application>` tag, include
```xml
<uses-permission android:name="android.permission.RECORD_AUDIO" />

View File

@@ -1,117 +0,0 @@
package org.jitsi.meet.sdk;
import static android.Manifest.permission.POST_NOTIFICATIONS;
import static android.Manifest.permission.RECORD_AUDIO;
import android.content.Context;
import android.content.pm.PackageManager;
import android.os.Build;
import androidx.annotation.NonNull;
import androidx.annotation.RequiresApi;
import com.facebook.react.ReactActivity;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod;
import com.facebook.react.module.annotations.ReactModule;
import com.facebook.react.modules.core.PermissionListener;
import org.jitsi.meet.sdk.log.JitsiMeetLogger;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
/**
* This class implements a ReactModule and it's
* responsible for launching/aborting a service when a conference is in progress.
*/
@ReactModule(name = JMOngoingConferenceModule.NAME)
class JMOngoingConferenceModule extends ReactContextBaseJavaModule {
public static final String NAME = "JMOngoingConference";
private static final int PERMISSIONS_REQUEST_CODE = (int) (Math.random() * Short.MAX_VALUE);
public JMOngoingConferenceModule(ReactApplicationContext reactContext) {
super(reactContext);
}
@ReactMethod
public void launch() {
Context context = getReactApplicationContext();
ReactActivity reactActivity = (ReactActivity) getCurrentActivity();
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) {
JMOngoingConferenceService.launch(context);
JitsiMeetLogger.i(NAME + " launch");
return;
}
PermissionListener listener = new PermissionListener() {
@Override
public boolean onRequestPermissionsResult(int i, String[] strings, int[] results) {
JitsiMeetLogger.i(NAME + " Permission callback received");
if (results == null || results.length == 0) {
JitsiMeetLogger.w(NAME + " Permission results are null or empty");
return true;
}
int counter = 0;
for (int result : results) {
if (result == PackageManager.PERMISSION_GRANTED) {
counter++;
}
}
JitsiMeetLogger.i(NAME + " Permissions granted: " + counter + "/" + results.length);
if (counter == results.length) {
JitsiMeetLogger.i(NAME + " All permissions granted, launching service");
JMOngoingConferenceService.launch(context);
} else {
JitsiMeetLogger.w(NAME + " Not all permissions were granted");
}
return true;
}
};
JitsiMeetLogger.i(NAME + " Checking Tiramisu permissions");
List<String> permissionsList = new ArrayList<>();
permissionsList.add(POST_NOTIFICATIONS);
permissionsList.add(RECORD_AUDIO);
String[] permissionsArray = new String[ permissionsList.size() ];
permissionsArray = permissionsList.toArray( permissionsArray );
try {
JitsiMeetLogger.i(NAME + " Requesting permissions: " + Arrays.toString(permissionsArray));
reactActivity.requestPermissions(permissionsArray, PERMISSIONS_REQUEST_CODE, listener);
} catch (Exception e) {
JitsiMeetLogger.e(e, NAME + " Error requesting permissions");
}
}
@ReactMethod
public void abort() {
Context context = getReactApplicationContext();
JMOngoingConferenceService.abort(context);
JitsiMeetLogger.i(NAME + " abort");
}
@NonNull
@Override
public String getName() {
return NAME;
}
}

View File

@@ -1,107 +0,0 @@
/*
* Copyright @ 2019-present 8x8, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jitsi.meet.sdk;
import android.app.Notification;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ServiceInfo;
import android.os.Build;
import android.os.IBinder;
import org.jitsi.meet.sdk.log.JitsiMeetLogger;
import java.util.Random;
/**
* This class implements an Android {@link Service}, a foreground one specifically, and it's
* responsible for presenting an ongoing notification when a conference is in progress.
* The service will help keep the app running while in the background.
*
* See: https://developer.android.com/guide/components/services
*/
public class JMOngoingConferenceService extends Service {
private static final String TAG = JMOngoingConferenceService.class.getSimpleName();
static final int NOTIFICATION_ID = new Random().nextInt(99999) + 10000;
public static void launch(Context context) {
try {
RNOngoingNotification.createOngoingConferenceNotificationChannel(context);
JitsiMeetLogger.i(TAG + " Notification channel creation completed");
} catch (Exception e) {
JitsiMeetLogger.e(e, TAG + " Error creating notification channel");
}
Intent intent = new Intent(context, JMOngoingConferenceService.class);
try {
context.startForegroundService(intent);
JitsiMeetLogger.i(TAG + " Starting foreground service");
} catch (RuntimeException e) {
// Avoid crashing due to ForegroundServiceStartNotAllowedException (API level 31).
// See: https://developer.android.com/guide/components/foreground-services#background-start-restrictions
JitsiMeetLogger.w(TAG + " Ongoing conference service not started", e);
}
}
public static void abort(Context context) {
Intent intent = new Intent(context, JMOngoingConferenceService.class);
context.stopService(intent);
}
@Override
public void onCreate() {
super.onCreate();
JitsiMeetLogger.i(TAG + " onCreate called");
try {
Notification notification = RNOngoingNotification.buildOngoingConferenceNotification(this);
JitsiMeetLogger.i(TAG + " Notification build result: " + (notification != null));
if (notification == null) {
stopSelf();
JitsiMeetLogger.w(TAG + " Couldn't start service, notification is null");
} else {
JitsiMeetLogger.i(TAG + " Starting service in foreground with notification");
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
startForeground(NOTIFICATION_ID, notification, ServiceInfo.FOREGROUND_SERVICE_TYPE_MEDIA_PLAYBACK | ServiceInfo.FOREGROUND_SERVICE_TYPE_MICROPHONE);
} else if (Build.VERSION.SDK_INT == Build.VERSION_CODES.Q) {
startForeground(NOTIFICATION_ID, notification, ServiceInfo.FOREGROUND_SERVICE_TYPE_MEDIA_PLAYBACK);
} else {
startForeground(NOTIFICATION_ID, notification);
}
}
} catch (Exception e) {
JitsiMeetLogger.e(e, TAG + " Error in onCreate");
}
}
@Override
public IBinder onBind(Intent intent) {
return null;
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
return START_NOT_STICKY;
}
}

View File

@@ -21,7 +21,6 @@ public class JitsiMeetReactNativePackage implements ReactPackage {
new AndroidSettingsModule(reactContext),
new AppInfoModule(reactContext),
new AudioModeModule(reactContext),
new JMOngoingConferenceModule(reactContext),
new JavaScriptSandboxModule(reactContext),
new LocaleDetector(reactContext),
new LogBridgeModule(reactContext),

View File

@@ -1,108 +0,0 @@
/*
* Copyright @ 2019-present 8x8, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jitsi.meet.sdk;
import android.app.Notification;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.os.Build;
import androidx.core.app.NotificationCompat;
import org.jitsi.meet.sdk.log.JitsiMeetLogger;
/**
* Helper class for creating the ongoing notification which is used with
* {@link JMOngoingConferenceService}. It allows the user to easily get back to the app
* and to hangup from within the notification itself.
*/
class RNOngoingNotification {
private static final String TAG = RNOngoingNotification.class.getSimpleName();
static final String RN_ONGOING_CONFERENCE_CHANNEL_ID = "OngoingConferenceChannel";
static void createOngoingConferenceNotificationChannel(Context context) {
NotificationManager notificationManager = (NotificationManager) context.getSystemService(Service.NOTIFICATION_SERVICE);
NotificationChannel channel = notificationManager.getNotificationChannel(RN_ONGOING_CONFERENCE_CHANNEL_ID);
if (channel != null) {
JitsiMeetLogger.i(TAG + " Notification channel already exists");
return;
}
channel = new NotificationChannel(
RN_ONGOING_CONFERENCE_CHANNEL_ID,
context.getString(R.string.ongoing_notification_channel_name),
NotificationManager.IMPORTANCE_DEFAULT
);
channel.enableVibration(true);
channel.setShowBadge(true);
channel.setLockscreenVisibility(Notification.VISIBILITY_PUBLIC);
notificationManager.createNotificationChannel(channel);
JitsiMeetLogger.i(TAG + " Notification channel created with importance: " + channel.getImportance());
}
static Notification buildOngoingConferenceNotification(Context context) {
if (context == null) {
JitsiMeetLogger.w(TAG + " Cannot create notification: no current context");
return null;
}
JitsiMeetLogger.i(TAG + " Creating notification with context: " + context);
// Creating an intent to launch app's main activity
Intent intent = context.getPackageManager()
.getLaunchIntentForPackage(context.getPackageName());
assert intent != null;
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP);
// Creating PendingIntent
PendingIntent pendingIntent = PendingIntent.getActivity(
context,
0,
intent,
PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE
);
NotificationCompat.Builder builder = new NotificationCompat.Builder(context, RN_ONGOING_CONFERENCE_CHANNEL_ID);
builder
.setCategory(NotificationCompat.CATEGORY_CALL)
.setContentTitle(context.getString(R.string.ongoing_notification_title))
.setContentText(context.getString(R.string.ongoing_notification_text))
.setPriority(NotificationCompat.PRIORITY_HIGH)
.setOngoing(true)
.setWhen(System.currentTimeMillis())
.setUsesChronometer(true)
.setAutoCancel(false)
.setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
.setOnlyAlertOnce(true)
.setSmallIcon(context.getResources().getIdentifier("ic_notification", "drawable", context.getPackageName()))
.setForegroundServiceBehavior(NotificationCompat.FOREGROUND_SERVICE_IMMEDIATE)
.setContentIntent(pendingIntent);
return builder.build();
}
}

View File

@@ -36,7 +36,6 @@
"moment-duration-format": "0.0.0",
"optional-require": "0.0.0",
"promise.allsettled": "0.0.0",
"promise.withresolvers": "0.0.0",
"punycode": "0.0.0",
"react-emoji-render": "0.0.0",
"react-i18next": "0.0.0",
@@ -59,6 +58,7 @@
"peerDependencies": {
"@amplitude/react-native": "0.0.0",
"@giphy/react-native-sdk": "0.0.0",
"@react-native/metro-config": "*",
"@react-native-async-storage/async-storage": "0.0.0",
"@react-native-clipboard/clipboard": "0.0.0",
"@react-native-community/netinfo": "0.0.0",

View File

@@ -1,13 +1,9 @@
const fs = require('fs');
const semver = require('semver');
const packageJSON = require('../package.json');
const SDKPackageJSON = require('./package.json');
// Skip checking these.
const skipDeps = [ 'react', 'react-native' ];
/**
* Merges the dependency versions from the root package.json with the dependencies of the SDK package.json.
*/
@@ -22,21 +18,15 @@ function mergeDependencyVersions() {
// Updates SDK peer dependencies.
for (const key in packageJSON.dependencies) {
if (SDKPackageJSON.peerDependencies.hasOwnProperty(key) && !skipDeps.includes(key)) {
SDKPackageJSON.peerDependencies[key] = packageJSON.dependencies[key];
if (SDKPackageJSON.peerDependencies.hasOwnProperty(key)) {
// Updates all peer dependencies except react and react-native.
if (key !== 'react' && key !== 'react-native') {
SDKPackageJSON.peerDependencies[key] = packageJSON.dependencies[key];
}
}
}
// Set RN peer dependency.
const rnVersion = semver.parse(packageJSON.dependencies['react-native']);
if (!rnVersion) {
throw new Error('failed to parse React Native version');
}
// In RN the "major" version is the Semver minor.
SDKPackageJSON.peerDependencies['react-native'] = `~0.${rnVersion.minor}.0`;
// Updates SDK overrides dependencies.
for (const key in packageJSON.overrides) {
if (SDKPackageJSON.overrides.hasOwnProperty(key)) {

View File

@@ -14,14 +14,24 @@ module.exports = {
project: [ './tsconfig.web.json', './tsconfig.native.json' ]
},
rules: {
// TODO: Remove these and fix the warnings
'@typescript-eslint/no-unsafe-function-type': 0,
'@typescript-eslint/no-wrapper-object-types': 0,
'@typescript-eslint/no-require-imports': 0
'@typescript-eslint/naming-convention': [
'error',
{
'selector': 'interface',
'format': [ 'PascalCase' ],
'custom': {
'regex': '^I[A-Z]',
'match': true
}
}
]
}
}
],
'rules': {
// XXX remove this eventually.
'react/jsx-indent-props': 0
},
'settings': {
'react': {
'version': 'detect'

View File

@@ -4,10 +4,8 @@ import ReactDOM from 'react-dom';
import AlwaysOnTop from './AlwaysOnTop';
// Render the main/root Component.
/* eslint-disable-next-line react/no-deprecated */
ReactDOM.render(<AlwaysOnTop />, document.getElementById('react'));
window.addEventListener(
'beforeunload',
/* eslint-disable-next-line react/no-deprecated */
() => ReactDOM.unmountComponentAtNode(document.getElementById('react') ?? document.body));

View File

@@ -311,7 +311,7 @@ export function createInviteDialogEvent(
* @returns {Object}
*/
export function createNetworkInfoEvent({ isOnline, networkType, details }:
{ details?: Object; isOnline: boolean; networkType?: string; }) {
{ details?: Object; isOnline: boolean; networkType?: string; }) {
const attributes: {
details?: Object;
isOnline: boolean;

View File

@@ -158,3 +158,4 @@ export function maybeRedirectToTokenAuthUrl(
return false;
}

View File

@@ -27,9 +27,11 @@ export interface IProps {
*/
export class AbstractApp<P extends IProps = IProps> extends BaseApp<P> {
/**
* The deferred for the initialization {{promise, resolve, reject}}.
* The deferred for the initialisation {{promise, resolve, reject}}.
*/
_init: PromiseWithResolvers<any>;
_init: {
promise: Promise<any>;
};
/**
* Initializes the app.

View File

@@ -140,7 +140,10 @@ function _getWebWelcomePageRoute(state: IReduxState) {
*
* @returns {Object}
*/
function _getEmptyRoute(): { component: React.ReactNode; href?: string; } {
function _getEmptyRoute(): {
component: React.ReactNode;
href?: string;
} {
return {
component: BlankPage,
href: undefined

View File

@@ -113,7 +113,8 @@ function _isMaybeSplitBrainError(getState: IStore['getState'], action: AnyAction
const { error } = action;
const isShardChangedError = error
&& error.message === 'item-not-found'
&& error.details?.shard_changed;
&& error.details
&& error.details.shard_changed;
if (isShardChangedError) {
const state = getState();

View File

@@ -38,6 +38,7 @@ import '../jaas/reducer';
import '../large-video/reducer';
import '../lobby/reducer';
import '../notifications/reducer';
import '../overlay/reducer';
import '../participants-pane/reducer';
import '../polls/reducer';
import '../polls-history/reducer';

View File

@@ -57,6 +57,7 @@ import { INoAudioSignalState } from '../no-audio-signal/reducer';
import { INoiseDetectionState } from '../noise-detection/reducer';
import { INoiseSuppressionState } from '../noise-suppression/reducer';
import { INotificationsState } from '../notifications/reducer';
import { IOverlayState } from '../overlay/reducer';
import { IParticipantsPaneState } from '../participants-pane/reducer';
import { IPollsState } from '../polls/reducer';
import { IPollsHistoryState } from '../polls-history/reducer';
@@ -146,6 +147,7 @@ export interface IReduxState {
'features/noise-detection': INoiseDetectionState;
'features/noise-suppression': INoiseSuppressionState;
'features/notifications': INotificationsState;
'features/overlay': IOverlayState;
'features/participants-pane': IParticipantsPaneState;
'features/polls': IPollsState;
'features/polls-history': IPollsHistoryState;

View File

@@ -121,7 +121,7 @@ MiddlewareRegistry.register(store => next => action => {
if (isTokenAuthEnabled(config)
&& config.tokenAuthUrlAutoRedirect
&& state['features/base/jwt'].jwt) {
// auto redirect is turned on and we have successfully logged in
// auto redirect is turned on and we have succesfully logged in
// let's mark that
dispatch(setTokenAuthUrlSuccess(true));
}

View File

@@ -14,6 +14,7 @@ import PersistenceRegistry from '../../redux/PersistenceRegistry';
import ReducerRegistry from '../../redux/ReducerRegistry';
import StateListenerRegistry from '../../redux/StateListenerRegistry';
import SoundCollection from '../../sounds/components/SoundCollection';
import { createDeferred } from '../../util/helpers';
import { appWillMount, appWillUnmount } from '../actions';
import logger from '../logger';
@@ -45,7 +46,9 @@ export default class BaseApp<P> extends Component<P, IState> {
/**
* The deferred for the initialisation {{promise, resolve, reject}}.
*/
_init: PromiseWithResolvers<any>;
_init: {
promise: Promise<any>;
};
/**
* Initializes a new {@code BaseApp} instance.
@@ -76,7 +79,7 @@ export default class BaseApp<P> extends Component<P, IState> {
* @see {@link #_initStorage}
* @type {Promise}
*/
this._init = Promise.withResolvers();
this._init = createDeferred<void>();
try {
await this._initStorage();

View File

@@ -248,7 +248,8 @@ function _conferenceFailed({ dispatch, getState }: IStore, next: Function, actio
}
!error.recoverable
&& conference?.leave(CONFERENCE_LEAVE_REASONS.UNRECOVERABLE_ERROR).catch((reason: Error) => {
&& conference
&& conference.leave(CONFERENCE_LEAVE_REASONS.UNRECOVERABLE_ERROR).catch((reason: Error) => {
// Even though we don't care too much about the failure, it may be
// good to know that it happen, so log it (on the info level).
logger.info('JitsiConference.leave() rejected with:', reason);
@@ -429,7 +430,7 @@ function _connectionFailed({ dispatch, getState }: IStore, next: Function, actio
} as INotificationProps;
const { locationURL = { href: '' } as URL } = getState()['features/base/connection'];
const { tenant = '' } = parseURIString(locationURL.href) || {};
const { tenant } = parseURIString(locationURL.href) || {};
if (tenant.startsWith('-') || tenant.endsWith('-')) {
notificationProps.descriptionKey = 'notify.invalidTenantHyphenDescription';
@@ -550,7 +551,7 @@ function _pinParticipant({ getState }: IStore, next: Function, action: AnyAction
const actionName = id ? ACTION_PINNED : ACTION_UNPINNED;
const local
= participantById?.local
|| (!id && pinnedParticipant?.local);
|| (!id && pinnedParticipant && pinnedParticipant.local);
let participantIdForEvent;
if (local) {

View File

@@ -495,7 +495,7 @@ function _conferenceJoined(state: IConferenceState, { conference }: { conference
* reduction of the specified action.
*/
function _conferenceLeftOrWillLeave(state: IConferenceState, { conference, type }:
{ conference: IJitsiConference; type: string; }) {
{ conference: IJitsiConference; type: string; }) {
const nextState = { ...state };
// The redux action CONFERENCE_LEFT is the last time that we should be

View File

@@ -1,6 +1,4 @@
import { ToolbarButton } from '../../toolbox/types';
import { ILoggingConfig } from '../logging/types';
import { DesktopSharingSourceType } from '../tracks/types';
type ButtonsWithNotifyClick = 'camera' |
'chat' |
@@ -217,10 +215,6 @@ export interface IConfig {
hideAutoAssignButton?: boolean;
hideJoinRoomButton?: boolean;
};
bridgeChannel?: {
ignoreDomain?: string;
preferSctp?: boolean;
};
buttonsWithNotifyClick?: Array<ButtonsWithNotifyClick | {
key: ButtonsWithNotifyClick;
preventExecution: boolean;
@@ -281,15 +275,11 @@ export interface IConfig {
max?: number;
min?: number;
};
desktopSharingSources?: Array<DesktopSharingSourceType>;
dialInConfCodeUrl?: string;
dialInNumbersUrl?: string;
dialOutAuthUrl?: string;
dialOutRegionUrl?: string;
disable1On1Mode?: boolean | null;
disableAEC?: boolean;
disableAGC?: boolean;
disableAP?: boolean;
disableAddingBackgroundImages?: boolean;
disableAudioLevels?: boolean;
disableBeforeUnloadHandlers?: boolean;
@@ -304,13 +294,11 @@ export interface IConfig {
disableJoinLeaveSounds?: boolean;
disableLocalVideoFlip?: boolean;
disableModeratorIndicator?: boolean;
disableNS?: boolean;
disablePolls?: boolean;
disableProfile?: boolean;
disableReactions?: boolean;
disableReactionsModeration?: boolean;
disableRecordAudioNotification?: boolean;
disableRemoteControl?: boolean;
disableRemoteMute?: boolean;
disableRemoveRaisedHandOnFocus?: boolean;
disableResponsiveTiles?: boolean;
@@ -328,7 +316,6 @@ export interface IConfig {
disableVirtualBackground?: boolean;
disabledNotifications?: Array<string>;
disabledSounds?: Array<Sounds>;
displayJids?: boolean;
doNotFlipLocalVideo?: boolean;
doNotStoreRoom?: boolean;
dropbox?: {
@@ -358,6 +345,7 @@ export interface IConfig {
maxMessagesPerSecond?: number;
numRequests?: number;
};
enableAutomaticUrlCopy?: boolean;
enableCalendarIntegration?: boolean;
enableClosePage?: boolean;
enableDisplayNameInStats?: boolean;
@@ -371,7 +359,6 @@ export interface IConfig {
enableOpusRed?: boolean;
enableRemb?: boolean;
enableSaveLogs?: boolean;
enableTalkWhileMuted?: boolean;
enableTcc?: boolean;
enableWebHIDFeature?: boolean;
enableWelcomePage?: boolean;
@@ -385,6 +372,7 @@ export interface IConfig {
faceCenteringThreshold?: number;
};
feedbackPercentage?: number;
fileRecordingsEnabled?: boolean;
fileRecordingsServiceEnabled?: boolean;
fileRecordingsServiceSharingEnabled?: boolean;
filmstrip?: {
@@ -394,11 +382,11 @@ export interface IConfig {
disabled?: boolean;
minParticipantCountForTopPanel?: number;
};
firefox_fake_device?: string;
flags?: {
ssrcRewritingEnabled: boolean;
};
focusUserJid?: string;
forceTurnRelay?: boolean;
gatherStats?: boolean;
giphy?: {
displayMode?: 'all' | 'tile' | 'chat';
@@ -438,7 +426,6 @@ export interface IConfig {
};
iAmRecorder?: boolean;
iAmSipGateway?: boolean;
ignoreStartMuted?: boolean;
inviteAppName?: string | null;
inviteServiceCallFlowsUrl?: string;
inviteServiceUrl?: string;
@@ -471,7 +458,6 @@ export interface IConfig {
};
localSubject?: string;
locationURL?: URL;
logging?: ILoggingConfig;
mainToolbarButtons?: Array<Array<string>>;
maxFullResolutionParticipants?: number;
microsoftApiApplicationClientID?: string;
@@ -493,7 +479,6 @@ export interface IConfig {
enabled?: boolean;
iceTransportPolicy?: string;
mobileCodecPreferenceOrder?: Array<string>;
mobileScreenshareCodec?: string;
stunServers?: Array<{ urls: string; }>;
};
participantMenuButtonsWithNotifyClick?: Array<string | ParticipantMenuButtonsWithNotifyClick | {
@@ -586,9 +571,7 @@ export interface IConfig {
subject?: string;
testing?: {
assumeBandwidth?: boolean;
debugAudioLevels?: boolean;
dumpTranscript?: boolean;
failICE?: boolean;
noAutoPlayVideo?: boolean;
p2pTestMode?: boolean;
skipInterimTranscriptions?: boolean;
@@ -636,13 +619,6 @@ export interface IConfig {
mobileCodecPreferenceOrder?: Array<string>;
persist?: boolean;
};
visitors?: {
enableMediaOnPromote?: {
audio?: boolean;
video?: boolean;
};
queueService: string;
};
watchRTCConfigParams?: IWatchRTCConfiguration;
webhookProxyUrl?: string;
webrtcIceTcpDisable?: boolean;

View File

@@ -22,7 +22,6 @@ export default [
'apiLogLevels',
'avgRtpStatsN',
'backgroundAlpha',
'brandingRoomAlias',
'breakoutRooms',
'bridgeChannel',
'buttonsWithNotifyClick',
@@ -78,6 +77,9 @@ export default [
'connectionIndicators',
'constraints',
'customToolbarButtons',
'brandingRoomAlias',
'debug',
'debugAudioLevels',
'deeplinking.disabled',
'deeplinking.desktop.enabled',
'defaultLocalDisplayName',
@@ -97,6 +99,7 @@ export default [
'disabledSounds',
'disableFilmstripAutohiding',
'disableInitialGUM',
'disableHPF',
'disableInviteFunctions',
'disableIncomingMessageSound',
'disableJoinLeaveSounds',
@@ -145,9 +148,14 @@ export default [
'enableNoAudioDetection',
'enableNoisyMicDetection',
'enableTcc',
'enableAutomaticUrlCopy',
'etherpad_base',
'faceLandmarks',
'failICE',
'feedbackPercentage',
'fileRecordingsEnabled',
'filmstrip',
'firefox_fake_device',
'flags',
'forceTurnRelay',
'gatherStats',
@@ -164,8 +172,10 @@ export default [
'hideAddRoomButton',
'hideEmailInSettings',
'hideLobbyButton',
'hosts',
'iAmRecorder',
'iAmSipGateway',
'iceTransportPolicy',
'ignoreStartMuted',
'inviteAppName',
'liveStreaming.enabled',
@@ -181,12 +191,7 @@ export default [
'notificationTimeouts',
'openSharedDocumentOnJoin',
'opusMaxAverageBitrate',
'p2p.backToP2PDelay',
'p2p.codecPreferenceOrder',
'p2p.enabled',
'p2p.iceTransportPolicy',
'p2p.mobileCodecPreferenceOrder',
'p2p.mobileScreenshareCodec',
'p2p',
'participantMenuButtonsWithNotifyClick',
'participantsPane',
'pcStatsInterval',
@@ -228,17 +233,7 @@ export default [
'useTurnUdp',
'videoQuality',
'visitors.enableMediaOnPromote',
'watchRTCConfigParams.allowBrowserLogCollection',
'watchRTCConfigParams.collectionInterval',
'watchRTCConfigParams.console',
'watchRTCConfigParams.debug',
'watchRTCConfigParams.keys',
'watchRTCConfigParams.logGetStats',
'watchRTCConfigParams.rtcApiKey',
'watchRTCConfigParams.rtcPeerId',
'watchRTCConfigParams.rtcRoomId',
'watchRTCConfigParams.rtcTags',
'watchRTCConfigParams.rtcToken',
'watchRTCConfigParams',
'webrtcIceTcpDisable',
'webrtcIceUdpDisable',
'whiteboard.enabled'

View File

@@ -89,7 +89,7 @@ export function _setDeeplinkingDefaults(deeplinking: IDeeplinkingConfig) {
android.downloadLink = android.downloadLink
|| 'https://play.google.com/store/apps/details?id=org.jitsi.meet';
android.appPackage = android.appPackage || 'org.jitsi.meet';
android.fDroidUrl = android.fDroidUrl || 'https://f-droid.org/packages/org.jitsi.meet/';
android.fDroidUrl = android.fDroidUrl || 'https://f-droid.org/en/packages/org.jitsi.meet/';
if (android.dynamicLink) {
android.dynamicLink.apn = android.dynamicLink.apn || 'org.jitsi.meet';
android.dynamicLink.appCode = android.dynamicLink.appCode || 'w2atb';

View File

@@ -22,12 +22,15 @@ export default [
'DISABLE_DOMINANT_SPEAKER_INDICATOR',
'DISABLE_FOCUS_INDICATOR',
'DISABLE_PRIVATE_MESSAGES',
'DISABLE_RINGING',
'DISABLE_TRANSCRIPTION_SUBTITLES',
'DISABLE_VIDEO_BACKGROUND',
'DISPLAY_WELCOME_PAGE_CONTENT',
'ENABLE_DIAL_OUT',
'ENABLE_FEEDBACK_ANIMATION',
'FILM_STRIP_MAX_HEIGHT',
'GENERATE_ROOMNAMES_ON_WELCOME_PAGE',
'HIDE_INVITE_MORE_HEADER',
'INDICATOR_FONT_SIZES',
'INITIAL_TOOLBAR_TIMEOUT',
'LANG_DETECTION',

View File

@@ -63,6 +63,7 @@ export interface IConfigState extends IConfig {
analysis?: {
obfuscateRoomName?: boolean;
};
disableRemoteControl?: boolean;
error?: Error;
oldConfig?: {
bosh?: string;
@@ -74,6 +75,13 @@ export interface IConfigState extends IConfig {
p2p?: object;
websocket?: string;
};
visitors?: {
enableMediaOnPromote?: {
audio?: boolean;
video?: boolean;
};
queueService: string;
};
}
ReducerRegistry.register<IConfigState>('features/base/config', (state = _getInitialState(), action): IConfigState => {

View File

@@ -1,11 +1,8 @@
/**
* Prefixes of devices that will be filtered from the device list.
*
* NOTE: It seems that the filtered devices can't be set
* NOTE: Currently we filter only 'Microsoft Teams Audio Device' virtual device. It seems that it can't be set
* as default device on the OS level and this use case is not handled in the code. If we add more device prefixes that
* can be default devices we should make sure to handle the default device use case.
*/
export const DEVICE_LABEL_PREFIXES_TO_IGNORE = [
'Microsoft Teams Audio Device',
'ZoomAudioDevice'
];
export const DEVICE_LABEL_PREFIXES_TO_IGNORE = [ 'Microsoft Teams Audio Device' ];

View File

@@ -9,7 +9,6 @@ import {
OPEN_SHEET
} from './actionTypes';
import { isDialogOpen } from './functions';
import logger from './logger';
/**
* Signals Dialog to close its dialog.
@@ -24,8 +23,6 @@ import logger from './logger';
* }}
*/
export function hideDialog(component?: ComponentType<any>) {
logger.info(`Hide dialog: ${getComponentDisplayName(component)}`);
return {
type: HIDE_DIALOG,
component
@@ -58,8 +55,6 @@ export function hideSheet() {
* }}
*/
export function openDialog(component: ComponentType<any>, componentProps?: Object) {
logger.info(`Open dialog: ${getComponentDisplayName(component)}`);
return {
type: OPEN_DIALOG,
component,
@@ -106,21 +101,3 @@ export function toggleDialog(component: ComponentType<any>, componentProps?: Obj
}
};
}
/**
* Extracts a printable name for a dialog component.
*
* @param {Object} component - The component to extract the name for.
*
* @returns {string} The display name.
*/
function getComponentDisplayName(component?: ComponentType<any>) {
if (!component) {
return '';
}
const name = component.displayName ?? component.name ?? 'Component';
return name.replace('withI18nextTranslation(Connect(', '') // dialogs with translations
.replace('))', ''); // dialogs with translations suffix
}

View File

@@ -23,7 +23,7 @@ MiddlewareRegistry.register(store => next => action => {
? action.value
: store.getState()['features/dynamic-branding'];
if (language && labels?.[language]) {
if (language && labels && labels[language]) {
changeLanguageBundle(language, labels[language])
.catch(err => {
logger.log('Error setting dynamic language bundle', err);

View File

@@ -237,7 +237,7 @@ function _undoOverwriteLocalParticipant(
* }}
*/
function _user2participant({ avatar, avatarUrl, email, id, name, 'hidden-from-recorder': hiddenFromRecorder }:
{ avatar?: string; avatarUrl?: string; email: string; 'hidden-from-recorder': string | boolean;
{ avatar?: string; avatarUrl?: string; email: string; 'hidden-from-recorder': string | boolean;
id: string; name: string; }) {
const participant: {
avatarURL?: string;

View File

@@ -25,6 +25,10 @@ export function createLocalTrack(type: string, deviceId: string | null, timeout?
JitsiMeetJS.createLocalTracks({
cameraDeviceId: deviceId,
devices: [ type ],
// eslint-disable-next-line camelcase
firefox_fake_device:
window.config?.firefox_fake_device,
micDeviceId: deviceId,
timeout,
...additionalOptions

View File

@@ -5,11 +5,10 @@ import ReducerRegistry from '../redux/ReducerRegistry';
import { equals, set } from '../redux/functions';
import { SET_LOGGING_CONFIG, SET_LOG_COLLECTOR } from './actionTypes';
import { ILoggingConfig, LogLevel } from './types';
const DEFAULT_LOGGING_CONFIG: ILoggingConfig = {
const DEFAULT_LOGGING_CONFIG = {
// default log level for the app and lib-jitsi-meet
defaultLogLevel: 'trace',
defaultLogLevel: 'trace' as LogLevel,
// Option to disable LogCollector (which stores the logs)
// disableLogCollector: true,
@@ -17,8 +16,8 @@ const DEFAULT_LOGGING_CONFIG: ILoggingConfig = {
loggers: {
// The following are too verbose in their logging with the
// {@link #defaultLogLevel}:
'modules/RTC/TraceablePeerConnection.js': 'info',
'modules/xmpp/strophe.util.js': 'log'
'modules/RTC/TraceablePeerConnection.js': 'info' as LogLevel,
'modules/xmpp/strophe.util.js': 'log' as LogLevel
}
};
@@ -40,11 +39,11 @@ const DEFAULT_STATE = {
// Reduce default verbosity on mobile, it kills performance.
if (navigator.product === 'ReactNative') {
const RN_LOGGERS: { [key: string]: LogLevel; } = {
'modules/sdp/SDPUtil.js': 'info',
'modules/xmpp/ChatRoom.js': 'warn',
'modules/xmpp/JingleSessionPC.js': 'info',
'modules/xmpp/strophe.jingle.js': 'info'
const RN_LOGGERS = {
'modules/sdp/SDPUtil.js': 'info' as LogLevel,
'modules/xmpp/ChatRoom.js': 'warn' as LogLevel,
'modules/xmpp/JingleSessionPC.js': 'info' as LogLevel,
'modules/xmpp/strophe.jingle.js': 'info' as LogLevel
};
DEFAULT_STATE.config.loggers = {
@@ -53,8 +52,16 @@ if (navigator.product === 'ReactNative') {
};
}
type LogLevel = 'trace' | 'log' | 'info' | 'warn' | 'error';
export interface ILoggingState {
config: ILoggingConfig;
config: {
defaultLogLevel: LogLevel;
disableLogCollector?: boolean;
loggers: {
[key: string]: LogLevel;
};
};
logCollector?: {
flush: () => void;
start: () => void;

View File

@@ -1,9 +0,0 @@
export type LogLevel = 'trace' | 'log' | 'info' | 'warn' | 'error';
export interface ILoggingConfig {
defaultLogLevel?: LogLevel;
disableLogCollector?: boolean;
loggers?: {
[key: string]: LogLevel;
};
}

View File

@@ -228,7 +228,7 @@ class AudioTrack extends Component<IProps> {
* @returns {void}
*/
_detachTrack(track?: ITrack) {
if (this._ref?.current && track?.jitsiTrack) {
if (this._ref?.current && track && track.jitsiTrack) {
clearTimeout(this._playTimeout);
this._playTimeout = undefined;
track.jitsiTrack.detach(this._ref.current);

View File

@@ -358,7 +358,7 @@ class Video extends Component<IProps> {
* @returns {void}
*/
_detachTrack(videoTrack?: Partial<ITrack>) {
if (this._videoElement && videoTrack?.jitsiTrack) {
if (this._videoElement && videoTrack && videoTrack.jitsiTrack) {
videoTrack.jitsiTrack.detach(this._videoElement);
}
}

View File

@@ -19,11 +19,11 @@ export const MEDIA_TYPE: {
AUDIO: MediaType;
SCREENSHARE: MediaType;
VIDEO: MediaType;
} = {
AUDIO: 'audio',
SCREENSHARE: 'screenshare',
VIDEO: 'video'
};
} = {
AUDIO: 'audio',
SCREENSHARE: 'screenshare',
VIDEO: 'video'
};
/* eslint-disable no-bitwise */

View File

@@ -3,6 +3,7 @@ import { AnyAction, combineReducers } from 'redux';
import { CONFERENCE_FAILED, CONFERENCE_LEFT } from '../conference/actionTypes';
import ReducerRegistry from '../redux/ReducerRegistry';
import { TRACK_REMOVED } from '../tracks/actionTypes';
import { DefferedPromise, createDeferred } from '../util/helpers';
import {
GUM_PENDING,
@@ -92,18 +93,17 @@ function _audio(state: IAudioState = _AUDIO_INITIAL_MEDIA_STATE, action: AnyActi
// initial track creation haven't been started we would wait for it to finish before starting to join the room.
// NOTE: The previous implementation was using the GUM promise from conference.init. But it turned out that connect
// may finish even before conference.init is executed.
const DEFAULT_INITIAL_PROMISE_STATE = Promise.withResolvers<IInitialGUMPromiseResult>();
const DEFAULT_INITIAL_PROMISE_STATE = createDeferred<IInitialGUMPromiseResult>();
/**
* Reducer for the common properties in media state.
* Reducer fot the common properties in media state.
*
* @param {ICommonState} state - Common media state.
* @param {Object} action - Action object.
* @param {string} action.type - Type of action.
* @returns {ICommonState}
*/
function _initialGUMPromise(
state: PromiseWithResolvers<IInitialGUMPromiseResult> | null = DEFAULT_INITIAL_PROMISE_STATE,
function _initialGUMPromise(state: DefferedPromise<IInitialGUMPromiseResult> | null = DEFAULT_INITIAL_PROMISE_STATE,
action: AnyAction) {
if (action.type === SET_INITIAL_GUM_PROMISE) {
return action.promise ?? null;
@@ -294,7 +294,7 @@ interface IVideoState {
export interface IMediaState {
audio: IAudioState;
initialGUMPromise: PromiseWithResolvers<IInitialGUMPromiseResult> | null;
initialGUMPromise: DefferedPromise<IInitialGUMPromiseResult> | null;
screenshare: IScreenshareState;
video: IVideoState;
}

View File

@@ -240,7 +240,7 @@ export function participantJoined(participant: IParticipant) {
// conference. The following check is really necessary because a
// JitsiConference may have moved into leaving but may still manage to
// sneak a PARTICIPANT_JOINED in if its leave is delayed for any purpose
// (which is not outrageous given that leaving involves network
// (which is not outragous given that leaving involves network
// requests.)
const stateFeaturesBaseConference
= getState()['features/base/conference'];

View File

@@ -15,6 +15,7 @@ import i18next from '../i18n/i18next';
import { MEDIA_TYPE, MediaType, VIDEO_TYPE } from '../media/constants';
import { toState } from '../redux/functions';
import { getScreenShareTrack, isLocalTrackMuted } from '../tracks/functions.any';
import { createDeferred } from '../util/helpers';
import {
JIGASI_PARTICIPANT_ICON,
@@ -126,7 +127,7 @@ export function getActiveSpeakersToBeDisplayed(stateful: IStateful) {
* @returns {Promise}
*/
export function getFirstLoadableAvatarUrl(participant: IParticipant, store: IStore) {
const deferred: any = Promise.withResolvers();
const deferred: any = createDeferred();
const fullPromise = deferred.promise
.then(() => _getFirstLoadableAvatarUrl(participant, store))
.then((result: any) => {

View File

@@ -52,7 +52,7 @@ StateListenerRegistry.register(
/**
* Compares the old and new screenshare lists provided and creates/removes the virtual screenshare participant
* tiles accordingly.
* tiles accodingly.
*
* @param {Array<string>} oldScreenshareSourceNames - List of old screenshare source names.
* @param {Array<string>} newScreenshareSourceNames - Current list of screenshare source names.

View File

@@ -340,7 +340,9 @@ class Popover extends Component<IProps, IState> {
_onTouchStart(event: TouchEvent) {
if (this.props.visible
&& !this.props.overflowDrawer
&& !this._contextMenuRef?.contains?.(event.target as Node)
&& this._contextMenuRef
&& this._contextMenuRef.contains
&& !this._contextMenuRef.contains(event.target as Node)
&& !this._containerRef?.current?.contains(event.target as Node)) {
this._onHideDialog();
}

View File

@@ -1,5 +1,5 @@
import clsx from 'clsx';
import React, { ReactNode, useEffect, useRef, useState } from 'react';
import React, { ReactNode } from 'react';
import { connect } from 'react-redux';
import { makeStyles } from 'tss-react/mui';
@@ -11,7 +11,6 @@ import { isButtonEnabled } from '../../../../toolbox/functions.web';
import { getConferenceName } from '../../../conference/functions';
import { PREMEETING_BUTTONS, THIRD_PARTY_PREJOIN_BUTTONS } from '../../../config/constants';
import { withPixelLineHeight } from '../../../styles/functions.web';
import Tooltip from '../../../tooltip/components/Tooltip';
import { isPreCallTestEnabled } from '../../functions';
import ConnectionStatus from './ConnectionStatus';
@@ -161,20 +160,15 @@ const useStyles = makeStyles()(theme => {
display: 'none'
}
},
roomNameContainer: {
width: '100%',
textAlign: 'center',
marginBottom: theme.spacing(4)
},
roomName: {
...withPixelLineHeight(theme.typography.heading5),
color: theme.palette.text01,
display: 'inline-block',
marginBottom: theme.spacing(4),
overflow: 'hidden',
textAlign: 'center',
textOverflow: 'ellipsis',
whiteSpace: 'nowrap',
maxWidth: '100%',
width: '100%'
}
};
});
@@ -201,19 +195,6 @@ const PreMeetingScreen = ({
backgroundSize: 'cover'
} : {};
const roomNameRef = useRef<HTMLSpanElement | null>(null);
const [ isOverflowing, setIsOverflowing ] = useState(false);
useEffect(() => {
if (roomNameRef.current) {
const element = roomNameRef.current;
const elementStyles = window.getComputedStyle(element);
const elementWidth = Math.floor(parseFloat(elementStyles.width));
setIsOverflowing(element.scrollWidth > elementWidth + 1);
}
}, [ _roomName ]);
return (
<div className = { clsx('premeeting-screen', classes.container, className) }>
<div style = { style }>
@@ -225,22 +206,8 @@ const PreMeetingScreen = ({
{title}
</h1>
{_roomName && (
<span className = { classes.roomNameContainer }>
{isOverflowing ? (
<Tooltip content = { _roomName }>
<span
className = { classes.roomName }
ref = { roomNameRef }>
{_roomName}
</span>
</Tooltip>
) : (
<span
className = { classes.roomName }
ref = { roomNameRef }>
{_roomName}
</span>
)}
<span className = { classes.roomName }>
{_roomName}
</span>
)}
{children}
@@ -292,4 +259,3 @@ function mapStateToProps(state: IReduxState, ownProps: Partial<IProps>) {
}
export default connect(mapStateToProps)(PreMeetingScreen);

View File

@@ -227,14 +227,14 @@ class MeetingsList extends Component<IProps> {
<Container
className = { rootClassName }
key = { index }
onClick = { onPress }
tabIndex = { 0 }>
onClick = { onPress }>
<Container className = 'right-column'>
<Text
className = 'title'
onClick = { onPress }
onKeyPress = { onKeyPress }
role = 'button'>
role = 'button'
tabIndex = { 0 }>
{ title }
</Text>
{

View File

@@ -41,8 +41,8 @@ export function formatURLText(text = '') {
}
if (!result) {
// This will be the case for invalid URLs or URLs without a host (emails for example). In this case due to
// the issue with PunycodeJS that truncates parts of the text when there is '@' we split the text by '@'
// This will be the case for invalid URLs or URLs without a host (emails for example). In this case beacuse
// of the issue with PunycodeJS that truncates parts of the text when there is '@' we split the text by '@'
// and use punycode for every separate part to prevent homograph attacks.
result = text.split('@').map(punycode.toASCII)
.join('@');
@@ -59,7 +59,7 @@ export function formatURLText(text = '') {
*/
export function getSupportUrl(stateful: IStateful) {
// TODO: Once overwriting through interface config is completely gone we should think of a way to be able to set
// TODO: Once overwriting trough interface config is completelly gone we should think of a way to be able to set
// the value in the branding and not return the default value from interface config.
return toState(stateful)['features/dynamic-branding'].supportUrl || interfaceConfig?.SUPPORT_URL;
}

View File

@@ -12,7 +12,7 @@ declare let __DEV__: any;
/**
* Mixed type of the element (subtree) config. If it's a {@code boolean} (and is
* {@code true}), we persist the entire subtree. If it's an {@code Object}, we
* persist a filtered subtree based on the properties of the config object.
* perist a filtered subtree based on the properties of the config object.
*/
declare type ElementConfig = boolean | Object;

View File

@@ -96,8 +96,8 @@ class StateListenerRegistry {
* @returns {void}
*/
_listener({ prevSelections, store }: {
prevSelections: Map<SelectorListener, any>;
store: Store<any, any>;
prevSelections: Map<SelectorListener, any>;
store: Store<any, any>;
}) {
for (const selectorListener of this._selectorListeners) {
const prevSelection = prevSelections.get(selectorListener);

View File

@@ -5,7 +5,7 @@ import { setAspectRatio, setReducedUI } from './actions';
/**
* Middleware that handles window dimension changes and updates the aspect ratio and
* Middleware that handles widnow dimension changes and updates the aspect ratio and
* reduced UI modes accordingly.
*
* @param {Store} store - The redux store.

View File

@@ -375,7 +375,7 @@ export default class AbstractButton<P extends IProps, S = any> extends Component
// blur after click to release focus from button to allow PTT.
// @ts-ignore
e?.currentTarget?.blur?.();
e?.currentTarget?.blur && e.currentTarget.blur();
}
/**

View File

@@ -86,16 +86,12 @@ export function createDesiredLocalTracks(...desiredTypes: any) {
dispatch(destroyLocalDesktopTrackIfExists());
if (desiredTypes.length === 0) {
const { startSilent } = state['features/base/config'];
const { video } = state['features/base/media'];
if (!startSilent) {
// Always create the audio track early, even if it will be muted.
// This fixes a timing issue when adding the track to the conference which
// manifests primarily on iOS 15.
// Unless we are silent, of course.
desiredTypes.push(MEDIA_TYPE.AUDIO);
}
// XXX: Always create the audio track early, even if it will be muted.
// This fixes a timing issue when adding the track to the conference which
// manifests primarily on iOS 15.
desiredTypes.push(MEDIA_TYPE.AUDIO);
// XXX When the app is coming into the foreground from the
// background in order to handle a URL, it may realize the new
@@ -838,16 +834,6 @@ export function toggleCamera() {
const localVideoTrack = getLocalVideoTrack(tracks)?.jitsiTrack;
const currentFacingMode = localVideoTrack.getCameraFacingMode();
const { localFlipX } = state['features/base/settings'];
/**
* FIXME: Ideally, we should be dispatching {@code replaceLocalTrack} here,
* but it seems to not trigger the re-rendering of the local video on Chrome;
* could be due to a plan B vs unified plan issue. Therefore, we use the legacy
* method defined in conference.js that manually takes care of updating the local
* video as well.
*/
await APP.conference.useVideoStream(null);
const targetFacingMode = currentFacingMode === CAMERA_FACING_MODE.USER
? CAMERA_FACING_MODE.ENVIRONMENT
: CAMERA_FACING_MODE.USER;
@@ -857,7 +843,6 @@ export function toggleCamera() {
const newVideoTrack = await createLocalTrack('video', null, null, { facingMode: targetFacingMode });
// FIXME: See above.
await APP.conference.useVideoStream(newVideoTrack);
dispatch(replaceLocalTrack(localVideoTrack, newVideoTrack));
};
}

View File

@@ -359,7 +359,8 @@ export function createInitialAVTracks(options: ICreateInitialTracksOptions, reco
return (dispatch: IStore['dispatch'], _getState: IStore['getState']) => {
const {
devices,
timeout
timeout,
firePermissionPromptIsShownEvent
} = options;
dispatch(gumPending(devices, IGUMPendingState.PENDING_UNMUTE));
@@ -399,14 +400,16 @@ export function createInitialAVTracks(options: ICreateInitialTracksOptions, reco
if (devices.includes(MEDIA_TYPE.AUDIO)) {
gUMPromises.push(createLocalTracksF({
devices: [ MEDIA_TYPE.AUDIO ],
timeout
timeout,
firePermissionPromptIsShownEvent
}));
}
if (devices.includes(MEDIA_TYPE.VIDEO)) {
gUMPromises.push(createLocalTracksF({
devices: [ MEDIA_TYPE.VIDEO ],
timeout
timeout,
firePermissionPromptIsShownEvent
}));
}

View File

@@ -17,7 +17,10 @@ export * from './functions.any';
* and/or 'video'.
* @param {string|null} [options.micDeviceId] - Microphone device id or
* {@code undefined} to use app's settings.
* @param {number|undefined} [options.timeout] - A timeout for JitsiMeetJS.createLocalTracks used to create the tracks.
* @param {number|undefined} [oprions.timeout] - A timeout for JitsiMeetJS.createLocalTracks used to create the tracks.
* @param {boolean} [options.firePermissionPromptIsShownEvent] - Whether lib-jitsi-meet
* should check for a {@code getUserMedia} permission prompt and fire a
* corresponding event.
* @param {IStore} store - The redux store in the context of which the function
* is to execute and from which state such as {@code config} is to be retrieved.
* @returns {Promise<JitsiLocalTrack[]>}

View File

@@ -31,7 +31,10 @@ export * from './functions.any';
* and/or 'video'.
* @param {string|null} [options.micDeviceId] - Microphone device id or
* {@code undefined} to use app's settings.
* @param {number|undefined} [options.timeout] - A timeout for JitsiMeetJS.createLocalTracks used to create the tracks.
* @param {number|undefined} [oprions.timeout] - A timeout for JitsiMeetJS.createLocalTracks used to create the tracks.
* @param {boolean} [options.firePermissionPromptIsShownEvent] - Whether lib-jitsi-meet
* should check for a {@code getUserMedia} permission prompt and fire a
* corresponding event.
* @param {IStore} store - The redux store in the context of which the function
* is to execute and from which state such as {@code config} is to be retrieved.
* @param {boolean} recordTimeMetrics - If true time metrics will be recorded.
@@ -42,6 +45,7 @@ export function createLocalTracksF(options: ITrackOptions = {}, store?: IStore,
const {
desktopSharingSourceDevice,
desktopSharingSources,
firePermissionPromptIsShownEvent,
timeout
} = options;
@@ -60,6 +64,7 @@ export function createLocalTracksF(options: ITrackOptions = {}, store?: IStore,
const {
desktopSharingFrameRate,
firefox_fake_device, // eslint-disable-line camelcase
resolution
} = state['features/base/config'];
const constraints = options.constraints ?? state['features/base/config'].constraints;
@@ -85,6 +90,8 @@ export function createLocalTracksF(options: ITrackOptions = {}, store?: IStore,
devices: options.devices?.slice(0),
effects,
facingMode: options.facingMode || getCameraFacingMode(state),
firefox_fake_device, // eslint-disable-line camelcase
firePermissionPromptIsShownEvent,
micDeviceId,
resolution,
timeout
@@ -139,6 +146,7 @@ export function createPrejoinTracks() {
if (requestedAudio || requestedVideo) {
tryCreateLocalTracks = createLocalTracksF({
devices: initialDevices,
firePermissionPromptIsShownEvent: true,
timeout
}, APP.store)
.catch(async (err: Error) => {
@@ -155,6 +163,7 @@ export function createPrejoinTracks() {
if (requestedAudio) {
gUMPromises.push(createLocalTracksF({
devices: [ MEDIA_TYPE.AUDIO ],
firePermissionPromptIsShownEvent: true,
timeout
}));
}
@@ -162,6 +171,7 @@ export function createPrejoinTracks() {
if (requestedVideo) {
gUMPromises.push(createLocalTracksF({
devices: [ MEDIA_TYPE.VIDEO ],
firePermissionPromptIsShownEvent: true,
timeout
}));
}

View File

@@ -12,9 +12,10 @@ export interface ITrackOptions {
};
};
desktopSharingSourceDevice?: string;
desktopSharingSources?: Array<DesktopSharingSourceType>;
desktopSharingSources?: string[];
devices?: string[];
facingMode?: string;
firePermissionPromptIsShownEvent?: boolean;
micDeviceId?: string | null;
timeout?: number;
}
@@ -67,16 +68,15 @@ export interface IToggleScreenSharingOptions {
shareOptions: IShareOptions;
}
export type DesktopSharingSourceType = 'screen' | 'window';
export interface IShareOptions {
desktopSharingSourceDevice?: string;
desktopSharingSources?: Array<DesktopSharingSourceType>;
desktopSharingSources?: string[];
desktopStream?: any;
}
export interface ICreateInitialTracksOptions {
devices: Array<MediaType>;
firePermissionPromptIsShownEvent?: boolean;
timeout?: number;
}

View File

@@ -62,7 +62,7 @@ const IconButton: React.FC<IIconButtonProps> = ({
underlayColor = { underlayColor }>
<Icon
color = { color }
size = { size ?? 20 }
size = { 20 || size }
src = { src } />
</TouchableHighlight>
);

View File

@@ -244,9 +244,9 @@ const ContextMenu = ({
list: Element | null,
currentFocus: Element | null,
traversalFunction: (
list: Element | null,
currentFocus: Element | null
) => Element | null
list: Element | null,
currentFocus: Element | null
) => Element | null
) => {
let wrappedOnce = false;
let nextFocus = traversalFunction(list, currentFocus);

View File

@@ -141,7 +141,7 @@ const useStyles = makeStyles()(theme => {
});
interface IObject {
[key: string]: unknown;
[key: string]: string | string[] | boolean | number | number[] | {} | undefined;
}
export interface IDialogTab<P> {

Some files were not shown because too many files have changed in this diff Show More