Compare commits

..

1 Commits

Author SHA1 Message Date
Saúl Ibarra Corretgé
d2521bc67a feat(android) bump minimum API level to 24
Some of our dependencies (most notably WebRTC) have dropped it and we
can no longer claim to support API level 23).
2023-05-01 11:06:44 +02:00
357 changed files with 3162 additions and 4207 deletions

View File

@@ -16,8 +16,7 @@
android:configChanges="keyboard|keyboardHidden|orientation|screenLayout|screenSize|smallestScreenSize|uiMode"
android:exported="true"
android:label="@string/app_name"
android:launchMode="singleInstance"
android:taskAffinity=""
android:launchMode="singleTask"
android:name=".MainActivity"
android:resizeableActivity="true"
android:supportsPictureInPicture="true"

View File

@@ -45,12 +45,6 @@ class AudioDeviceHandlerGeneric implements
*/
private AudioModeModule module;
/**
* Constant defining a Hearing Aid. Only available on API level >= 28.
* The value of: AudioDeviceInfo.TYPE_HEARING_AID
*/
private static final int TYPE_HEARING_AID = 23;
/**
* Constant defining a USB headset. Only available on API level >= 26.
* The value of: AudioDeviceInfo.TYPE_USB_HEADSET
@@ -91,7 +85,6 @@ class AudioDeviceHandlerGeneric implements
break;
case AudioDeviceInfo.TYPE_WIRED_HEADPHONES:
case AudioDeviceInfo.TYPE_WIRED_HEADSET:
case TYPE_HEARING_AID:
case TYPE_USB_HEADSET:
devices.add(AudioModeModule.DEVICE_HEADPHONES);
break;

View File

@@ -559,7 +559,7 @@ export default {
);
}
let tryCreateLocalTracks = Promise.resolve([]);
let tryCreateLocalTracks;
// On Electron there is no permission prompt for granting permissions. That's why we don't need to
// spend much time displaying the overlay screen. If GUM is not resolved within 15 seconds it will
@@ -600,65 +600,76 @@ export default {
return [];
});
} else if (requestedAudio || requestedVideo) {
} else if (!requestedAudio && !requestedVideo) {
// Resolve with no tracks
tryCreateLocalTracks = Promise.resolve([]);
} else {
tryCreateLocalTracks = createLocalTracksF({
devices: initialDevices,
timeout,
firePermissionPromptIsShownEvent: true
})
.catch(async error => {
if (error.name === JitsiTrackErrors.TIMEOUT && !browser.isElectron()) {
errors.audioAndVideoError = error;
.catch(err => {
if (requestedAudio && requestedVideo) {
// Try audio only...
errors.audioAndVideoError = err;
if (err.name === JitsiTrackErrors.TIMEOUT && !browser.isElectron()) {
// In this case we expect that the permission prompt is still visible. There is no point of
// executing GUM with different source. Also at the time of writing the following
// inconsistency have been noticed in some browsers - if the permissions prompt is visible
// and another GUM is executed the prompt does not change its content but if the user
// clicks allow the user action isassociated with the latest GUM call.
errors.audioOnlyError = err;
errors.videoOnlyError = err;
return [];
}
return createLocalTracksF(audioOptions);
} else if (requestedAudio && !requestedVideo) {
errors.audioOnlyError = err;
return [];
} else if (requestedVideo && !requestedAudio) {
errors.videoOnlyError = err;
return [];
}
logger.error('Should never happen');
})
.catch(err => {
// Log this just in case...
if (!requestedAudio) {
logger.error('The impossible just happened', err);
}
errors.audioOnlyError = err;
// Try video only...
return requestedVideo
? createLocalTracksF({
devices: [ MEDIA_TYPE.VIDEO ],
firePermissionPromptIsShownEvent: true
})
: [];
})
.catch(err => {
// Log this just in case...
if (!requestedVideo) {
logger.error('The impossible just happened', err);
}
errors.videoOnlyError = err;
return [];
}
// Retry with separate gUM calls.
const gUMPromises = [];
const tracks = [];
if (requestedAudio) {
gUMPromises.push(createLocalTracksF(audioOptions));
}
if (requestedVideo) {
gUMPromises.push(createLocalTracksF({
devices: [ MEDIA_TYPE.VIDEO ],
timeout,
firePermissionPromptIsShownEvent: true
}));
}
const results = await Promise.allSettled(gUMPromises);
let errorMsg;
results.forEach((result, idx) => {
if (result.status === 'fulfilled') {
tracks.push(result.value[0]);
} else {
errorMsg = result.reason;
const isAudio = idx === 0;
logger.error(`${isAudio ? 'Audio' : 'Video'} track creation failed with error ${errorMsg}`);
if (isAudio) {
errors.audioOnlyError = errorMsg;
} else {
errors.videoOnlyError = errorMsg;
}
}
});
if (errors.audioOnlyError && errors.videoOnlyError) {
errors.audioAndVideoError = errorMsg;
}
return tracks;
});
}
// 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.
// Hide the permissions prompt/overlay as soon as the tracks are
// created. Don't wait for the connection to be made, since in some
// cases, when auth is required, for instance, that won't happen until
// the user inputs their credentials, but the dialog would be
// overshadowed by the overlay.
tryCreateLocalTracks.then(tracks => {
APP.store.dispatch(mediaPermissionPromptVisibilityChanged(false));
@@ -799,51 +810,43 @@ export default {
const initialOptions = {
startAudioOnly: config.startAudioOnly,
startScreenSharing: config.startScreenSharing,
startWithAudioMuted: getStartWithAudioMuted(state) || isUserInteractionRequiredForUnmute(state),
startWithVideoMuted: getStartWithVideoMuted(state) || isUserInteractionRequiredForUnmute(state)
startWithAudioMuted: getStartWithAudioMuted(state)
|| isUserInteractionRequiredForUnmute(state),
startWithVideoMuted: getStartWithVideoMuted(state)
|| isUserInteractionRequiredForUnmute(state)
};
this.roomName = roomName;
try {
// Initialize the device list first. This way, when creating tracks based on preferred devices, loose label
// matching can be done in cases where the exact ID match is no longer available, such as -
// 1. When the camera device has switched USB ports.
// 2. When in startSilent mode we want to start with audio muted
// Initialize the device list first. This way, when creating tracks
// based on preferred devices, loose label matching can be done in
// cases where the exact ID match is no longer available, such as
// when the camera device has switched USB ports.
// when in startSilent mode we want to start with audio muted
await this._initDeviceList();
} catch (error) {
logger.warn('initial device list initialization failed', error);
}
// Filter out the local tracks based on various config options, i.e., when user joins muted or is muted by
// focus. However, audio track will always be created even though it is not added to the conference since we
// want audio related features (noisy mic, talk while muted, etc.) to work even if the mic is muted.
const handleInitialTracks = (options, tracks) => {
let localTracks = tracks;
// No local tracks are added when user joins as a visitor.
if (iAmVisitor(state)) {
return [];
}
if (options.startWithAudioMuted || room?.isStartAudioMuted()) {
const handleStartAudioMuted = (options, tracks) => {
if (options.startWithAudioMuted) {
// Always add the track on Safari because of a known issue where audio playout doesn't happen
// if the user joins audio and video muted, i.e., if there is no local media capture.
if (browser.isWebKitBased()) {
this.muteAudio(true, true);
} else {
localTracks = localTracks.filter(track => track.getType() !== MEDIA_TYPE.AUDIO);
return tracks.filter(track => track.getType() !== MEDIA_TYPE.AUDIO);
}
}
if (room?.isStartVideoMuted()) {
localTracks = localTracks.filter(track => track.getType() !== MEDIA_TYPE.VIDEO);
}
return localTracks;
return tracks;
};
if (isPrejoinPageVisible(state)) {
_connectionPromise = connect(roomName).then(c => {
// We want to initialize it early, in case of errors to be able to gather logs.
// we want to initialize it early, in case of errors to be able
// to gather logs
APP.connection = c;
return c;
@@ -856,28 +859,48 @@ export default {
APP.store.dispatch(makePrecallTest(this._getConferenceOptions()));
const { tryCreateLocalTracks, errors } = this.createInitialLocalTracks(initialOptions);
const localTracks = await tryCreateLocalTracks;
const tracks = await tryCreateLocalTracks;
// Initialize device list a second time to ensure device labels get populated in case of an initial gUM
// acceptance; otherwise they may remain as empty strings.
// Initialize device list a second time to ensure device labels
// get populated in case of an initial gUM acceptance; otherwise
// they may remain as empty strings.
this._initDeviceList(true);
if (isPrejoinPageVisible(state)) {
return APP.store.dispatch(initPrejoin(localTracks, errors));
return APP.store.dispatch(initPrejoin(tracks, errors));
}
logger.debug('Prejoin screen no longer displayed at the time when tracks were created');
this._displayErrorsForCreateInitialLocalTracks(errors);
return this._setLocalAudioVideoStreams(handleInitialTracks(initialOptions, localTracks));
let localTracks = handleStartAudioMuted(initialOptions, tracks);
// In case where gUM is slow and resolves after the startAudio/VideoMuted coming from jicofo, we can be
// join unmuted even though jicofo had instruct us to mute, so let's respect that before passing the tracks
if (!browser.isWebKitBased()) {
if (room?.isStartAudioMuted()) {
localTracks = localTracks.filter(track => track.getType() !== MEDIA_TYPE.AUDIO);
}
}
if (room?.isStartVideoMuted()) {
localTracks = localTracks.filter(track => track.getType() !== MEDIA_TYPE.VIDEO);
}
// Do not add the tracks if the user has joined the call as a visitor.
if (iAmVisitor(state)) {
return Promise.resolve();
}
return this._setLocalAudioVideoStreams(localTracks);
}
const [ tracks, con ] = await this.createInitialLocalTracksAndConnect(roomName, initialOptions);
this._initDeviceList(true);
return this.startConference(con, handleInitialTracks(initialOptions, tracks));
return this.startConference(con, handleStartAudioMuted(initialOptions, tracks));
},
/**
@@ -1050,7 +1073,7 @@ export default {
*/
muteVideo(mute, showUI = true) {
if (this.videoSwitchInProgress) {
logger.warn('muteVideo - unable to perform operations while video switch is in progress');
console.warn('muteVideo - unable to perform operations while video switch is in progress');
return;
}

View File

@@ -74,6 +74,10 @@
a:active {
color: black;
}
&::-webkit-scrollbar-corner {
background: #3a3a3a;
}
}

1
globals.d.ts vendored
View File

@@ -21,7 +21,6 @@ declare global {
JitsiMeetElectron?: any;
// selenium tests handler
_sharedVideoPlayer: any;
alwaysOnTop: { api: any };
}
interface Document {

View File

@@ -134,7 +134,7 @@ PODS:
- AppAuth/Core (~> 1.6)
- GTMSessionFetcher/Core (< 3.0, >= 1.5)
- GTMSessionFetcher/Core (2.3.0)
- JitsiWebRTC (111.0.2)
- JitsiWebRTC (111.0.1)
- libwebp (1.2.4):
- libwebp/demux (= 1.2.4)
- libwebp/mux (= 1.2.4)
@@ -725,7 +725,7 @@ SPEC CHECKSUMS:
GoogleUtilities: 9aa0ad5a7bc171f8bae016300bfcfa3fb8425749
GTMAppAuth: 0ff230db599948a9ad7470ca667337803b3fc4dd
GTMSessionFetcher: 3a63d75eecd6aa32c2fc79f578064e1214dfdec2
JitsiWebRTC: 80f62908fcf2a1160e0d14b584323fb6e6be630b
JitsiWebRTC: 9619c1f71cc16eeca76df68aa2d213c6d63274a8
libwebp: f62cb61d0a484ba548448a4bd52aabf150ff6eef
nanopb: a0ba3315591a9ae0a16a309ee504766e90db0c96
ObjectiveDropboxOfficial: fe206ce8c0bc49976c249d472db7fdbc53ebbd53

View File

@@ -98,6 +98,7 @@ platform :ios do
demo_account_required: false,
distribute_external: true,
groups: ENV["JITSI_BETA_TESTING_GROUPS"],
reject_build_waiting_for_review: true,
uses_non_exempt_encryption: false
)

View File

@@ -870,11 +870,9 @@
"lookGood": "Your microphone is working properly",
"or": "or",
"premeeting": "Pre meeting",
"proceedAnyway": "Proceed anyway",
"screenSharingError": "Screen sharing error:",
"showScreen": "Enable pre meeting screen",
"startWithPhone": "Start with phone audio",
"unsafeRoomConsent": "I understand the risks, I want to join the meeting",
"videoOnlyError": "Video error:",
"videoTrackError": "Could not create video track.",
"viewAllNumbers": "view all numbers"
@@ -976,14 +974,8 @@
"security": {
"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 you meeting ",
"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.",
"prejoin": "Consider using a more unique meeting name.",
"welcome": "Consider using a more unique meeting name, or pick one of the suggestions."
}
"insecureRoomNameWarning": "The room name is unsafe. Unwanted participants may join your conference. Consider securing your meeting using the security button.",
"title": "Security Options"
},
"settings": {
"audio": "Audio",
@@ -1158,7 +1150,6 @@
"privateMessage": "Send private message",
"profile": "Edit your profile",
"raiseHand": "Raise your hand",
"reactions": "Reactions",
"reactionsMenu": "Reactions menu",
"recording": "Toggle recording",
"remoteMute": "Mute participant",
@@ -1256,7 +1247,6 @@
"reactionLike": "Send thumbs up reaction",
"reactionSilence": "Send silence reaction",
"reactionSurprised": "Send surprised reaction",
"reactions": "Reactions",
"security": "Security options",
"selectBackground": "Select background",
"shareRoom": "Invite someone",

View File

@@ -113,8 +113,6 @@ import { isAudioMuteButtonDisabled } from '../../react/features/toolbox/function
import { setTileView, toggleTileView } from '../../react/features/video-layout/actions.any';
import { muteAllParticipants } from '../../react/features/video-menu/actions';
import { setVideoQuality } from '../../react/features/video-quality/actions';
import { toggleWhiteboard } from '../../react/features/whiteboard/actions.any';
import { WhiteboardStatus } from '../../react/features/whiteboard/types';
import { getJitsiMeetTransport } from '../transport';
import {
@@ -835,9 +833,6 @@ function initCommands() {
} else {
logger.error(' End Conference not supported');
}
},
'toggle-whiteboard': () => {
APP.store.dispatch(toggleWhiteboard());
}
};
transport.on('event', ({ data, name }) => {
@@ -2019,20 +2014,6 @@ class API {
});
}
/**
* Notify external application (if API is enabled) if whiteboard state is
* changed.
*
* @param {WhiteboardStatus} status - The new whiteboard status.
* @returns {void}
*/
notifyWhiteboardStatusChanged(status: WhiteboardStatus) {
this._sendEvent({
name: 'whiteboard-status-changed',
status
});
}
/**
* Disposes the allocated resources.
*

View File

@@ -90,8 +90,7 @@ const commands = {
toggleSubtitles: 'toggle-subtitles',
toggleTileView: 'toggle-tile-view',
toggleVirtualBackgroundDialog: 'toggle-virtual-background',
toggleVideo: 'toggle-video',
toggleWhiteboard: 'toggle-whiteboard'
toggleVideo: 'toggle-video'
};
/**
@@ -155,8 +154,7 @@ const events = {
'subject-change': 'subjectChange',
'suspend-detected': 'suspendDetected',
'tile-view-changed': 'tileViewChanged',
'toolbar-button-clicked': 'toolbarButtonClicked',
'whiteboard-status-changed': 'whiteboardStatusChanged'
'toolbar-button-clicked': 'toolbarButtonClicked'
};
/**
@@ -316,7 +314,6 @@ export default class JitsiMeetExternalAPI extends EventEmitter {
* @param {string} [options.e2eeKey] - The key used for End-to-End encryption.
* THIS IS EXPERIMENTAL.
* @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, ...args) {
super();
@@ -334,8 +331,7 @@ export default class JitsiMeetExternalAPI extends EventEmitter {
devices,
userInfo,
e2eeKey,
release,
sandbox = ''
release
} = parseArguments(args);
const localStorageContent = jitsiLocalStorage.getItem('jitsiLocalStorage');
@@ -353,7 +349,7 @@ export default class JitsiMeetExternalAPI extends EventEmitter {
},
release
});
this._createIFrame(height, width, onload, sandbox);
this._createIFrame(height, width, onload);
this._transport = new Transport({
backend: new PostMessageTransportBackend({
postisOptions: {
@@ -386,12 +382,11 @@ export default class JitsiMeetExternalAPI extends EventEmitter {
* parseSizeParam for format details.
* @param {Function} onload - The function that will listen
* for onload event.
* @param {string} sandbox - Sandbox directive for the created iframe, if desired.
* @returns {void}
*
* @private
*/
_createIFrame(height, width, onload, sandbox) {
_createIFrame(height, width, onload) {
const frameName = `jitsiConferenceFrame${id}`;
this._frame = document.createElement('iframe');
@@ -402,10 +397,6 @@ export default class JitsiMeetExternalAPI extends EventEmitter {
this._frame.setAttribute('allowFullScreen', 'true');
this._frame.style.border = 0;
if (sandbox) {
this._frame.sandbox = sandbox;
}
if (onload) {
// waits for iframe resources to load
// and fires event when it is done

2249
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -65,7 +65,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/v1632.0.0+e4966db9/lib-jitsi-meet.tgz",
"lib-jitsi-meet": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v1626.0.0+a41aa571/lib-jitsi-meet.tgz",
"lodash": "4.17.21",
"moment": "2.29.4",
"moment-duration-format": "2.2.2",
@@ -76,7 +76,7 @@
"react": "17.0.2",
"react-dom": "17.0.2",
"react-emoji-render": "1.2.4",
"react-focus-lock": "2.9.4",
"react-focus-lock": "2.5.1",
"react-i18next": "10.11.4",
"react-linkify": "1.0.0-alpha",
"react-native": "0.68.6",
@@ -125,7 +125,7 @@
},
"devDependencies": {
"@babel/core": "7.16.0",
"@babel/eslint-parser": "7.21.8",
"@babel/eslint-parser": "7.16.0",
"@babel/plugin-proposal-export-default-from": "7.16.0",
"@babel/preset-env": "7.16.0",
"@babel/preset-flow": "7.16.0",
@@ -140,7 +140,6 @@
"@types/react-dom": "17.0.14",
"@types/react-linkify": "1.0.1",
"@types/react-native": "0.68.9",
"@types/react-native-keep-awake": "2.0.3",
"@types/react-native-video": "5.0.14",
"@types/react-redux": "7.1.24",
"@types/react-window": "1.8.5",
@@ -150,20 +149,20 @@
"@types/w3c-image-capture": "1.0.6",
"@types/w3c-web-hid": "1.0.3",
"@types/zxcvbn": "4.4.1",
"@typescript-eslint/eslint-plugin": "5.59.5",
"@typescript-eslint/parser": "5.59.5",
"@typescript-eslint/eslint-plugin": "5.30.5",
"@typescript-eslint/parser": "5.30.4",
"babel-loader": "8.2.3",
"babel-plugin-optional-require": "0.3.1",
"circular-dependency-plugin": "5.2.0",
"clean-css-cli": "4.3.0",
"css-loader": "3.6.0",
"eslint": "8.40.0",
"eslint": "8.35.0",
"eslint-plugin-flowtype": "8.0.3",
"eslint-plugin-import": "2.27.5",
"eslint-plugin-import": "2.25.2",
"eslint-plugin-jsdoc": "37.0.3",
"eslint-plugin-react": "7.32.2",
"eslint-plugin-react-native": "4.0.0",
"eslint-plugin-typescript-sort-keys": "2.3.0",
"eslint-plugin-react": "7.26.1",
"eslint-plugin-react-native": "3.11.0",
"eslint-plugin-typescript-sort-keys": "2.1.0",
"jetifier": "1.6.4",
"metro-react-native-babel-preset": "0.67.0",
"patch-package": "6.4.7",
@@ -171,8 +170,8 @@
"sass": "1.26.8",
"style-loader": "3.3.1",
"traverse": "0.6.6",
"ts-loader": "9.4.2",
"typescript": "5.0.4",
"ts-loader": "9.4.1",
"typescript": "4.7.4",
"unorm": "1.6.0",
"webpack": "5.76.0",
"webpack-bundle-analyzer": "4.4.2",
@@ -180,7 +179,7 @@
"webpack-dev-server": "4.7.3"
},
"overrides": {
"strophe.js@1.5.0": {
"strophe.js@1.6.0": {
"@xmldom/xmldom": "0.8.7"
}
},

View File

@@ -1,3 +1,5 @@
// @flow
import React, { Component } from 'react';
// We need to reference these files directly to avoid loading things that are not available
@@ -17,15 +19,15 @@ const TOOLBAR_TIMEOUT = 4000;
/**
* The type of the React {@code Component} state of {@link AlwaysOnTop}.
*/
interface IState {
avatarURL: string;
customAvatarBackgrounds: Array<string>;
displayName: string;
formattedDisplayName: string;
isVideoDisplayed: boolean;
userID: string;
visible: boolean;
}
type State = {
avatarURL: string,
customAvatarBackgrounds: Array<string>,
displayName: string,
formattedDisplayName: string,
isVideoDisplayed: boolean,
userID: string,
visible: boolean
};
/**
* Represents the always on top page.
@@ -33,7 +35,7 @@ interface IState {
* @class AlwaysOnTop
* @augments Component
*/
export default class AlwaysOnTop extends Component<any, IState> {
export default class AlwaysOnTop extends Component<*, State> {
_hovered: boolean;
/**
@@ -42,7 +44,7 @@ export default class AlwaysOnTop extends Component<any, IState> {
* @param {*} props - The read-only properties with which the new instance
* is to be initialized.
*/
constructor(props: any) {
constructor(props: *) {
super(props);
this.state = {
@@ -66,25 +68,28 @@ export default class AlwaysOnTop extends Component<any, IState> {
this._onMouseOver = this._onMouseOver.bind(this);
}
_avatarChangedListener: () => void;
/**
* Handles avatar changed api events.
*
* @returns {void}
*/
_avatarChangedListener({ avatarURL, id }: { avatarURL: string; id: string; }) {
_avatarChangedListener({ avatarURL, id }) {
if (api._getOnStageParticipant() === id
&& avatarURL !== this.state.avatarURL) {
this.setState({ avatarURL });
}
}
_displayNameChangedListener: () => void;
/**
* Handles display name changed api events.
*
* @returns {void}
*/
_displayNameChangedListener({ displayname, formattedDisplayName, id }: { displayname: string;
formattedDisplayName: string; id: string; }) {
_displayNameChangedListener({ displayname, formattedDisplayName, id }) {
if (api._getOnStageParticipant() === id
&& (formattedDisplayName !== this.state.formattedDisplayName
|| displayname !== this.state.displayName)) {
@@ -113,6 +118,8 @@ export default class AlwaysOnTop extends Component<any, IState> {
TOOLBAR_TIMEOUT);
}
_videoChangedListener: () => void;
/**
* Handles large video changed api events.
*
@@ -134,6 +141,8 @@ export default class AlwaysOnTop extends Component<any, IState> {
});
}
_mouseMove: () => void;
/**
* Handles mouse move events.
*
@@ -143,6 +152,8 @@ export default class AlwaysOnTop extends Component<any, IState> {
this.state.visible || this.setState({ visible: true });
}
_onMouseOut: () => void;
/**
* Toolbar mouse out handler.
*
@@ -152,6 +163,8 @@ export default class AlwaysOnTop extends Component<any, IState> {
this._hovered = false;
}
_onMouseOver: () => void;
/**
* Toolbar mouse over handler.
*
@@ -216,7 +229,7 @@ export default class AlwaysOnTop extends Component<any, IState> {
this._hideToolbarAfterTimeout();
api.getCustomAvatarBackgrounds()
.then((res: { avatarBackgrounds?: string[]; }) =>
.then(res =>
this.setState({
customAvatarBackgrounds: res.avatarBackgrounds || []
}))
@@ -229,7 +242,7 @@ export default class AlwaysOnTop extends Component<any, IState> {
* @inheritdoc
* @returns {void}
*/
componentDidUpdate(_prevProps: any, prevState: IState) {
componentDidUpdate(prevProps: *, prevState: State) {
if (!prevState.visible && this.state.visible) {
this._hideToolbarAfterTimeout();
}

View File

@@ -1,9 +1,11 @@
// @flow
import React, { Component } from 'react';
// We need to reference these files directly to avoid loading things that are not available
// in this environment (e.g. JitsiMeetJS or interfaceConfig)
import { IconMic, IconMicSlash } from '../base/icons/svg';
import { IProps } from '../base/toolbox/components/AbstractButton';
import type { Props } from '../base/toolbox/components/AbstractButton';
import ToolbarButton from './ToolbarButton';
@@ -12,25 +14,23 @@ const { api } = window.alwaysOnTop;
/**
* The type of the React {@code Component} state of {@link AudioMuteButton}.
*/
interface IState {
type State = {
/**
* Whether audio is available is not.
*/
audioAvailable: boolean;
audioAvailable: boolean,
/**
* Whether audio is muted or not.
*/
audioMuted: boolean;
}
type Props = Partial<IProps>;
audioMuted: boolean
};
/**
* Stateless "mute/unmute audio" button for the Always-on-Top windows.
*/
export default class AudioMuteButton extends Component<Props, IState> {
export default class AudioMuteButton extends Component<Props, State> {
icon = IconMic;
toggledIcon = IconMicSlash;
accessibilityLabel = 'Audio mute';
@@ -38,7 +38,7 @@ export default class AudioMuteButton extends Component<Props, IState> {
/**
* Initializes a new {@code AudioMuteButton} instance.
*
* @param {IProps} props - The React {@code Component} props to initialize
* @param {Props} props - The React {@code Component} props to initialize
* the new {@code AudioMuteButton} instance with.
*/
constructor(props: Props) {
@@ -94,23 +94,27 @@ export default class AudioMuteButton extends Component<Props, IState> {
this._audioMutedListener);
}
_audioAvailabilityListener: ({ available: boolean }) => void;
/**
* Handles audio available api events.
*
* @param {{ available: boolean }} status - The new available status.
* @returns {void}
*/
_audioAvailabilityListener({ available }: { available: boolean; }) {
_audioAvailabilityListener({ available }) {
this.setState({ audioAvailable: available });
}
_audioMutedListener: ({ muted: boolean }) => void;
/**
* Handles audio muted api events.
*
* @param {{ muted: boolean }} status - The new muted status.
* @returns {void}
*/
_audioMutedListener({ muted }: { muted: boolean; }) {
_audioMutedListener({ muted }) {
this.setState({ audioMuted: muted });
}
@@ -140,14 +144,16 @@ export default class AudioMuteButton extends Component<Props, IState> {
* Changes the muted state.
*
* @override
* @param {boolean} _audioMuted - Whether audio should be muted or not.
* @param {boolean} audioMuted - Whether audio should be muted or not.
* @protected
* @returns {void}
*/
_setAudioMuted(_audioMuted: boolean) {
_setAudioMuted(audioMuted: boolean) { // eslint-disable-line no-unused-vars
this.state.audioAvailable && api.executeCommand('toggleAudio');
}
_onClick: () => {};
/**
* Handles clicking / pressing the button, and toggles the audio mute state
* accordingly.
@@ -167,13 +173,11 @@ export default class AudioMuteButton extends Component<Props, IState> {
render() {
const toggled = this._isAudioMuted();
return (
<ToolbarButton
accessibilityLabel = { this.accessibilityLabel }
disabled = { this._isDisabled() }
icon = { toggled ? this.toggledIcon : this.icon }
onClick = { this._onClick }
toggled = { toggled } />
);
return (<ToolbarButton
accessibilityLabel = { this.accessibilityLabel }
disabled = { this._isDisabled() }
icon = { toggled ? this.toggledIcon : this.icon }
onClick = { this._onClick }
toggled = { toggled } />);
}
}

View File

@@ -1,20 +1,19 @@
// @flow
import React, { Component } from 'react';
// We need to reference these files directly to avoid loading things that are not available
// in this environment (e.g. JitsiMeetJS or interfaceConfig)
import { IconHangup } from '../base/icons/svg';
import { IProps } from '../base/toolbox/components/AbstractButton';
import type { Props } from '../base/toolbox/components/AbstractButton';
import ToolbarButton from './ToolbarButton';
const { api } = window.alwaysOnTop;
type Props = Partial<IProps>;
/**
* Stateless hangup button for the Always-on-Top windows.
*/
export default class HangupButton extends Component<Props> {
export default class HangupButton extends Component<Props, *> {
accessibilityLabel = 'Hangup';
icon = IconHangup;
@@ -22,7 +21,7 @@ export default class HangupButton extends Component<Props> {
/**
* Initializes a new {@code HangupButton} instance.
*
* @param {IProps} props - The React {@code Component} props to initialize
* @param {Props} props - The React {@code Component} props to initialize
* the new {@code HangupButton} instance with.
*/
constructor(props: Props) {
@@ -32,6 +31,8 @@ export default class HangupButton extends Component<Props> {
this._onClick = this._onClick.bind(this);
}
_onClick: () => {};
/**
* Handles clicking / pressing the button, and disconnects the conference.
*
@@ -49,12 +50,10 @@ export default class HangupButton extends Component<Props> {
* @returns {ReactElement}
*/
render() {
return (
<ToolbarButton
accessibilityLabel = { this.accessibilityLabel }
customClass = 'hangup-button'
icon = { this.icon }
onClick = { this._onClick } />
);
return (<ToolbarButton
accessibilityLabel = { this.accessibilityLabel }
customClass = 'hangup-button'
icon = { this.icon }
onClick = { this._onClick } />);
}
}

View File

@@ -1,3 +1,5 @@
// @flow
import React, { Component } from 'react';
import AudioMuteButton from './AudioMuteButton';
@@ -7,30 +9,30 @@ import VideoMuteButton from './VideoMuteButton';
/**
* The type of the React {@code Component} props of {@link Toolbar}.
*/
interface IProps {
type Props = {
/**
* Additional CSS class names to add to the root of the toolbar.
*/
className: string;
className: string,
/**
* Callback invoked when no longer moused over the toolbar.
*/
onMouseOut: (e?: React.MouseEvent) => void;
onMouseOut: Function,
/**
* Callback invoked when the mouse has moved over the toolbar.
*/
onMouseOver: (e?: React.MouseEvent) => void;
}
onMouseOver: Function
};
/**
* Represents the toolbar in the Always On Top window.
*
* @augments Component
*/
export default class Toolbar extends Component<IProps> {
export default class Toolbar extends Component<Props> {
/**
* Implements React's {@link Component#render()}.
*

View File

@@ -2,38 +2,38 @@ import React, { useCallback } from 'react';
import Icon from '../base/icons/components/Icon';
interface IProps {
type Props = {
/**
* Accessibility label for button.
*/
accessibilityLabel: string;
accessibilityLabel: string,
/**
* An extra class name to be added at the end of the element's class name
* in order to enable custom styling.
*/
customClass?: string;
customClass?: string,
/**
* Whether or not the button is disabled.
*/
disabled?: boolean;
/**
* Button icon.
*/
icon: Function;
disabled?: boolean,
/**
* Click handler.
*/
onClick: (e?: React.MouseEvent) => void;
onClick: Function,
/**
* Button icon.
*/
icon: Object,
/**
* Whether or not the button is toggled.
*/
toggled?: boolean;
toggled?: boolean
}
const ToolbarButton = ({
@@ -43,7 +43,7 @@ const ToolbarButton = ({
onClick,
icon,
toggled = false
}: IProps) => {
}: Props) => {
const onKeyPress = useCallback(event => {
if (event.key === 'Enter' || event.key === ' ') {
event.preventDefault();

View File

@@ -1,16 +1,15 @@
// @flow
import React, { Component } from 'react';
// We need to reference these files directly to avoid loading things that are not available
// in this environment (e.g. JitsiMeetJS or interfaceConfig)
import { IconVideo, IconVideoOff } from '../base/icons/svg';
import { IProps } from '../base/toolbox/components/AbstractButton';
import type { Props } from '../base/toolbox/components/AbstractButton';
import ToolbarButton from './ToolbarButton';
const { api } = window.alwaysOnTop;
type Props = Partial<IProps>;
/**
* The type of the React {@code Component} state of {@link VideoMuteButton}.
*/
@@ -19,12 +18,12 @@ type State = {
/**
* Whether video is available is not.
*/
videoAvailable: boolean;
videoAvailable: boolean,
/**
* Whether video is muted or not.
*/
videoMuted: boolean;
videoMuted: boolean
};
/**
@@ -120,34 +119,40 @@ export default class VideoMuteButton extends Component<Props, State> {
* Changes the muted state.
*
* @override
* @param {boolean} _videoMuted - Whether video should be muted or not.
* @param {boolean} videoMuted - Whether video should be muted or not.
* @protected
* @returns {void}
*/
_setVideoMuted(_videoMuted: boolean) {
_setVideoMuted(videoMuted: boolean) { // eslint-disable-line no-unused-vars
this.state.videoAvailable && api.executeCommand('toggleVideo', false, true);
}
_videoAvailabilityListener: ({ available: boolean }) => void;
/**
* Handles video available api events.
*
* @param {{ available: boolean }} status - The new available status.
* @returns {void}
*/
_videoAvailabilityListener({ available }: { available: boolean; }) {
_videoAvailabilityListener({ available }) {
this.setState({ videoAvailable: available });
}
_videoMutedListener: ({ muted: boolean }) => void;
/**
* Handles video muted api events.
*
* @param {{ muted: boolean }} status - The new muted status.
* @returns {void}
*/
_videoMutedListener({ muted }: { muted: boolean; }) {
_videoMutedListener({ muted }) {
this.setState({ videoMuted: muted });
}
_onClick: () => {};
/**
* Handles clicking / pressing the button, and toggles the video mute state
* accordingly.
@@ -168,13 +173,11 @@ export default class VideoMuteButton extends Component<Props, State> {
render() {
const toggled = this._isVideoMuted();
return (
<ToolbarButton
accessibilityLabel = { this.accessibilityLabel }
disabled = { this._isDisabled() }
icon = { toggled ? this.toggledIcon : this.icon }
onClick = { this._onClick }
toggled = { toggled } />
);
return (<ToolbarButton
accessibilityLabel = { this.accessibilityLabel }
disabled = { this._isDisabled() }
icon = { toggled ? this.toggledIcon : this.icon }
onClick = { this._onClick }
toggled = { toggled } />);
}
}

View File

@@ -1,11 +1,14 @@
// @flow
import React from 'react';
import ReactDOM from 'react-dom';
import AlwaysOnTop from './AlwaysOnTop';
// Render the main/root Component.
// $FlowExpectedError
ReactDOM.render(<AlwaysOnTop />, document.getElementById('react'));
window.addEventListener(
'beforeunload',
() => ReactDOM.unmountComponentAtNode(document.getElementById('react') ?? document.body));
() => ReactDOM.unmountComponentAtNode(document.getElementById('react')));

View File

@@ -1,3 +1,4 @@
/* eslint-disable lines-around-comment */
import { setRoom } from '../base/conference/actions';
import {
configWillLoad,
@@ -12,7 +13,6 @@ import {
import { connect, disconnect, setLocationURL } from '../base/connection/actions';
import { loadConfig } from '../base/lib-jitsi-meet/functions.native';
import { createDesiredLocalTracks } from '../base/tracks/actions';
import isInsecureRoomName from '../base/util/isInsecureRoomName';
import { parseURLParams } from '../base/util/parseURLParams';
import {
appendURLParam,
@@ -20,11 +20,14 @@ import {
parseURIString,
toURLString
} from '../base/util/uri';
// @ts-ignore
import { isPrejoinPageEnabled } from '../mobile/navigation/functions';
import {
goBackToRoot,
navigateRoot
// @ts-ignore
} from '../mobile/navigation/rootNavigationContainerRef';
// @ts-ignore
import { screen } from '../mobile/navigation/routes';
import { clearNotifications } from '../notifications/actions';
@@ -52,7 +55,7 @@ export function appNavigate(uri?: string, options: IReloadNowOptions = {}) {
// If the specified location (URI) does not identify a host, use the app's
// default.
if (!location?.host) {
if (!location || !location.host) {
const defaultLocation = parseURIString(getDefaultURL(getState));
if (location) {
@@ -137,14 +140,10 @@ export function appNavigate(uri?: string, options: IReloadNowOptions = {}) {
dispatch(setRoom(room));
if (room) {
if (isInsecureRoomName(room)) {
navigateRoot(screen.unsafeRoomWarning);
return;
}
dispatch(createDesiredLocalTracks());
dispatch(clearNotifications());
// @ts-ignore
const { hidePrejoin } = options;
if (!hidePrejoin && isPrejoinPageEnabled(getState())) {

View File

@@ -49,7 +49,7 @@ export function appNavigate(uri?: string) {
// If the specified location (URI) does not identify a host, use the app's
// default.
if (!location?.host) {
if (!location || !location.host) {
const defaultLocation = parseURIString(getDefaultURL(getState));
if (location) {

View File

@@ -5,6 +5,8 @@ import { IStateful } from '../base/app/types';
import { isRoomValid } from '../base/conference/functions';
import { isSupportedBrowser } from '../base/environment/environment';
import { toState } from '../base/redux/functions';
// eslint-disable-next-line lines-around-comment
// @ts-ignore
import Conference from '../conference/components/web/Conference';
import { getDeepLinkingPage } from '../deep-linking/functions';
import UnsupportedDesktopBrowser from '../unsupported-browser/components/UnsupportedDesktopBrowser';

View File

@@ -1,5 +1,4 @@
import '../base/devices/reducer';
import '../base/premeeting/reducer';
import '../base/tooltip/reducer';
import '../e2ee/reducer';
import '../face-landmarks/reducer';

View File

@@ -20,7 +20,6 @@ import { ILoggingState } from '../base/logging/reducer';
import { IMediaState } from '../base/media/reducer';
import { INetInfoState } from '../base/net-info/reducer';
import { IParticipantsState } from '../base/participants/reducer';
import { IPreMeetingState } from '../base/premeeting/types';
import { IResponsiveUIState } from '../base/responsive-ui/reducer';
import { ISettingsState } from '../base/settings/reducer';
import { ISoundsState } from '../base/sounds/reducer';
@@ -111,7 +110,6 @@ export interface IReduxState {
'features/base/net-info': INetInfoState;
'features/base/no-src-data': INoSrcDataState;
'features/base/participants': IParticipantsState;
'features/base/premeeting': IPreMeetingState;
'features/base/responsive-ui': IResponsiveUIState;
'features/base/settings': ISettingsState;
'features/base/sounds': ISoundsState;

View File

@@ -27,13 +27,13 @@ export function cancelLogin() {
// a reaction to CONNECTION_FAILED). Since the
// app/user is going to navigate to WelcomePage, the SDK
// clients/consumers need an event.
const { error = { recoverable: undefined }, passwordRequired }
const { error, passwordRequired }
= getState()['features/base/connection'];
passwordRequired
&& dispatch(
connectionFailed(
passwordRequired,
passwordRequired, // @ts-ignore
set(error, 'recoverable', false) as any));
};
}

View File

@@ -1,2 +1,4 @@
export { default as LoginDialog } from './native/LoginDialog';
// eslint-disable-next-line lines-around-comment
// @ts-ignore
export { default as WaitForOwnerDialog } from './native/WaitForOwnerDialog';

View File

@@ -24,7 +24,7 @@ import {
openLoginDialog,
openWaitForOwnerDialog,
stopWaitForOwner,
waitForOwner } from './actions.native';
waitForOwner } from './actions.native'; // @ts-ignore
import { LoginDialog, WaitForOwnerDialog } from './components';
/**

View File

@@ -292,7 +292,7 @@ export function getVisitorOptions(stateful: IStateful, params: Array<string>) {
const config = toState(stateful)['features/base/config'];
if (!config?.hosts) {
if (!config || !config.hosts) {
logger.warn('Wrong configuration, missing hosts.');
return;
@@ -300,8 +300,7 @@ export function getVisitorOptions(stateful: IStateful, params: Array<string>) {
if (!vnode) {
// this is redirecting back to main, lets restore config
// not updating disableFocus, as if the room capacity is full the promotion to the main room will fail
// and the visitor will be redirected back to a vnode from jicofo
// no point of updating disableFocus, we can skip the initial iq to jicofo
if (config.oldConfig && username) {
return {
hosts: {
@@ -311,7 +310,6 @@ export function getVisitorOptions(stateful: IStateful, params: Array<string>) {
focusUserJid: focusJid,
disableLocalStats: false,
bosh: config.oldConfig.bosh && appendURLParam(config.oldConfig.bosh, 'customusername', username),
p2p: config.oldConfig.p2p,
websocket: config.oldConfig.websocket
&& appendURLParam(config.oldConfig.websocket, 'customusername', username),
oldConfig: undefined // clears it up
@@ -328,7 +326,6 @@ export function getVisitorOptions(stateful: IStateful, params: Array<string>) {
},
focusUserJid: config.focusUserJid,
bosh: config.bosh,
p2p: config.p2p,
websocket: config.websocket
};
@@ -344,10 +341,6 @@ export function getVisitorOptions(stateful: IStateful, params: Array<string>) {
disableFocus: true, // This flag disables sending the initial conference request
disableLocalStats: true,
bosh: config.bosh && appendURLParam(config.bosh, 'vnode', vnode),
p2p: {
...config.p2p,
enabled: false
},
websocket: config.websocket && appendURLParam(config.websocket, 'vnode', vnode)
};
}

View File

@@ -289,12 +289,6 @@ function _conferenceJoined({ dispatch, getState }: IStore, next: Function, actio
dispatch(conferenceWillLeave(conference));
};
if (!iAmVisitor(getState())) {
// if a visitor is promoted back to main room and want to join an empty breakout room
// we need to send iq to jicofo, so it can join/create the breakout room
dispatch(overwriteConfig({ disableFocus: false }));
}
// @ts-ignore
window.addEventListener(disableBeforeUnloadHandlers ? 'unload' : 'beforeunload', beforeUnloadHandler);

View File

@@ -46,7 +46,6 @@ export interface IJitsiConference {
authenticateAndUpgradeRole: Function;
avModerationApprove: Function;
avModerationReject: Function;
callUUID?: string;
createVideoSIPGWSession: Function;
dial: Function;
disableAVModeration: Function;

View File

@@ -1,4 +1,4 @@
// @ts-expect-error
// @ts-ignore
import { jitsiLocalStorage } from '@jitsi/js-utils';
import { IStore } from '../../app/types';

View File

@@ -18,7 +18,6 @@ type ToolbarButtons = 'camera' |
'participants-pane' |
'profile' |
'raisehand' |
'reactions' |
'recording' |
'security' |
'select-background' |
@@ -170,7 +169,6 @@ export interface IConfig {
}>;
callDisplayName?: string;
callFlowsEnabled?: boolean;
callHandle?: string;
callStatsConfigParams?: {
additionalIDs?: {
customerID?: string;
@@ -192,7 +190,6 @@ export interface IConfig {
};
callStatsID?: string;
callStatsSecret?: string;
callUUID?: string;
channelLastN?: number;
chromeExtensionBanner?: {
chromeExtensionsInfo?: Array<{ id: string; path: string; }>;
@@ -408,7 +405,6 @@ export interface IConfig {
legalUrls?: {
helpCentre: string;
privacy: string;
security: string;
terms: string;
};
liveStreaming?: {

View File

@@ -64,7 +64,7 @@ export const THIRD_PARTY_PREJOIN_BUTTONS = [ 'microphone', 'camera', 'select-bac
/**
* The toolbar buttons to show when in visitors mode.
*/
export const VISITORS_MODE_BUTTONS = [ 'chat', 'hangup', 'raisehand', 'settings', 'tileview' ];
export const VISITORS_MODE_BUTTONS = [ 'chat', 'hangup', 'raisehand', 'tileview' ];
/**
* The set of feature flags.

View File

@@ -1,7 +1,7 @@
// @ts-expect-error
// @ts-ignore
import Bourne from '@hapi/bourne';
// eslint-disable-next-line lines-around-comment
// @ts-expect-error
// @ts-ignore
import { jitsiLocalStorage } from '@jitsi/js-utils';
import _ from 'lodash';

View File

@@ -84,7 +84,6 @@ export interface IConfigState extends IConfig {
domain: string;
muc: string;
};
p2p?: object;
websocket?: string;
};
}

View File

@@ -36,7 +36,7 @@ type Props = {
/**
* Function to render a bottom sheet footer element, if necessary.
*/
renderFooter?: () => React.ReactNode;
renderFooter?: Function;
/**
* Function to render a bottom sheet header element, if necessary.
@@ -109,7 +109,9 @@ class BottomSheet extends PureComponent<Props> {
} = this.props;
return (
<SlidingView
<SlidingView // @ts-ignore
accessibilityRole = 'menu'
accessibilityViewIsModal = { true }
onHide = { this._onCancel }
position = 'bottom'
show = { Boolean(showSlidingView) }>

View File

@@ -18,7 +18,7 @@ interface IProps extends AbstractProps, WithTranslation {
/**
* The dialog descriptionKey.
*/
descriptionKey?: string;
descriptionKey: string;
/**
* An optional initial value to initiate the field with.

View File

@@ -1,4 +1,4 @@
// @ts-expect-error
// @ts-ignore
import { randomInt } from '@jitsi/js-utils/random';
import React, { Component } from 'react';
import { WithTranslation } from 'react-i18next';
@@ -11,6 +11,7 @@ import { isFatalJitsiConnectionError } from '../../../lib-jitsi-meet/functions.n
import { hideDialog } from '../../actions';
import logger from '../../logger';
// @ts-ignore
import ConfirmDialog from './ConfirmDialog';
@@ -38,7 +39,9 @@ interface IPageReloadDialogState {
* Shows a warning message and counts down towards the re-load.
*/
class PageReloadDialog extends Component<IPageReloadDialogProps, IPageReloadDialogState> {
_interval?: number;
// @ts-ignore
_interval: IntervalID;
_timeoutSeconds: number;
/**
@@ -102,7 +105,7 @@ class PageReloadDialog extends Component<IPageReloadDialogProps, IPageReloadDial
_onCancel() {
const { dispatch } = this.props;
clearInterval(this._interval ?? 0);
clearInterval(this._interval);
dispatch(appNavigate(undefined));
return true;
@@ -142,7 +145,7 @@ class PageReloadDialog extends Component<IPageReloadDialogProps, IPageReloadDial
_onReloadNow() {
const { dispatch } = this.props;
clearInterval(this._interval ?? 0);
clearInterval(this._interval);
dispatch(reloadNow());
return true;
@@ -197,6 +200,8 @@ function mapStateToProps(state: IReduxState) {
const { fatalError } = state['features/overlay'];
const fatalConnectionError
// @ts-ignore
= connectionError && isFatalJitsiConnectionError(connectionError);
const fatalConfigError = fatalError === configError;

View File

@@ -1,7 +1,7 @@
// @ts-expect-error
// @ts-ignore
import Bourne from '@hapi/bourne';
// eslint-disable-next-line lines-around-comment
// @ts-expect-error
// @ts-ignore
import { jitsiLocalStorage } from '@jitsi/js-utils/jitsi-local-storage';
import { browser } from '../lib-jitsi-meet';

View File

@@ -1,4 +1,4 @@
// @ts-expect-error
// @ts-ignore
import jwtDecode from 'jwt-decode';
import { IReduxState } from '../../app/types';

View File

@@ -1,4 +1,4 @@
// @ts-expect-error
// @ts-ignore
import jwtDecode from 'jwt-decode';
import { AnyAction } from 'redux';

View File

@@ -1,9 +1,10 @@
// @ts-expect-error
// @ts-ignore
import { jitsiLocalStorage } from '@jitsi/js-utils';
import { IStore } from '../../app/types';
import { isOnline } from '../net-info/selectors';
// @ts-ignore
import JitsiMeetJS from './_';
import {
LIB_DID_DISPOSE,

View File

@@ -1,5 +1,4 @@
import { IStateful } from '../app/types';
import { ConnectionFailedError } from '../connection/actions.any';
import { toState } from '../redux/functions';
// @ts-ignore
@@ -92,7 +91,7 @@ export function isFatalJitsiConferenceError(error: Error | string) {
* indicates a fatal {@code JitsiConnection} error, {@code true}; otherwise,
* {@code false}.
*/
export function isFatalJitsiConnectionError(error: Error | string | ConnectionFailedError) {
export function isFatalJitsiConnectionError(error: Error | string) {
if (typeof error !== 'string') {
error = error.name; // eslint-disable-line no-param-reassign
}

View File

@@ -1,4 +1,4 @@
// @ts-expect-error
// @ts-ignore
import Bourne from '@hapi/bourne';
import { NativeModules } from 'react-native';

View File

@@ -1,5 +1,6 @@
// Re-export JitsiMeetJS from the library lib-jitsi-meet to (the other features
// of) the project jitsi-meet.
// @ts-ignore
import JitsiMeetJS from './_';
export { JitsiMeetJS as default };

View File

@@ -6,6 +6,7 @@ import { SET_NETWORK_INFO } from '../net-info/actionTypes';
import { PARTICIPANT_LEFT } from '../participants/actionTypes';
import MiddlewareRegistry from '../redux/MiddlewareRegistry';
// @ts-ignore
import JitsiMeetJS from './_';
import { LIB_WILL_INIT } from './actionTypes';
import { disposeLib, initLib } from './actions';

View File

@@ -90,7 +90,7 @@ export default class JitsiMeetLogStorage {
storeLogsCallstats(logEntries: Array<string | any>) {
const conference = getCurrentConference(this.getState());
if (!conference?.isCallstatsEnabled()) {
if (!conference || !conference.isCallstatsEnabled()) {
// Discard the logs if CallStats is not enabled.
return;
}

View File

@@ -1,6 +1,6 @@
import { NativeModules } from 'react-native';
// eslint-disable-next-line lines-around-comment
// @ts-expect-error
// @ts-ignore
import { format } from 'util';
// Some code adapted from https://github.com/houserater/react-native-lumberjack

View File

@@ -1,4 +1,4 @@
// @ts-expect-error
// @ts-ignore
import Logger, { getLogger as _getLogger } from '@jitsi/logger';
import _ from 'lodash';

View File

@@ -1,4 +1,4 @@
// @ts-expect-error
// @ts-ignore
import Logger from '@jitsi/logger';
import { IStore } from '../../app/types';

View File

@@ -1,2 +1,5 @@
// @ts-ignore
export { default as Audio } from './native/Audio';
// @ts-ignore
export { default as Video } from './native/Video';

View File

@@ -7,6 +7,7 @@ import { IReduxState, IStore } from '../../../../app/types';
import { ASPECT_RATIO_WIDE } from '../../../responsive-ui/constants';
import { storeVideoTransform } from '../../actions';
// @ts-ignore
import styles from './styles';
@@ -124,12 +125,12 @@ class VideoTransform extends Component<IProps, IState> {
/**
* The gesture handler object.
*/
gestureHandlers: any;
gestureHandlers: Object;
/**
* The initial distance of the fingers on pinch start.
*/
initialDistance?: number;
initialDistance: number;
/**
* The initial position of the finger on touch start.
@@ -233,6 +234,8 @@ class VideoTransform extends Component<IProps, IState> {
videoTransformedViewContainerStyles,
style
] }
// @ts-ignore
{ ...this.gestureHandlers.panHandlers }>
<SafeAreaView
edges = { [ 'bottom', 'left' ] }
@@ -486,7 +489,7 @@ class VideoTransform extends Component<IProps, IState> {
* @param {?Object | number} value - The value of the gesture, if any.
* @returns {void}
*/
_onGesture(type: string, value?: any) {
_onGesture(type: string, value: any) {
let transform;
switch (type) {
@@ -597,7 +600,7 @@ class VideoTransform extends Component<IProps, IState> {
this._onGesture('scale', scale);
}
} else if (gestureState.numberActiveTouches === 1
&& isNaN(this.initialDistance ?? 0)
&& isNaN(this.initialDistance)
&& this._didMove(gestureState)) {
// this is a move event
const position = this._getTouchPosition(evt);
@@ -620,9 +623,11 @@ class VideoTransform extends Component<IProps, IState> {
*/
_onPanResponderRelease() {
if (this.lastTap && Date.now() - this.lastTap < TAP_TIMEOUT_MS) {
// @ts-ignore
this._onGesture('press');
}
// @ts-ignore
delete this.initialDistance;
this.initialPosition = {
x: 0,

View File

@@ -185,7 +185,7 @@ class AudioTrack extends Component<IProps> {
* @returns {void}
*/
_attachTrack(track?: ITrack) {
if (!track?.jitsiTrack) {
if (!track || !track.jitsiTrack) {
return;
}

View File

@@ -321,7 +321,7 @@ class Video extends Component<IProps> {
* @returns {void}
*/
_attachTrack(videoTrack?: Partial<ITrack>) {
if (!videoTrack?.jitsiTrack) {
if (!videoTrack || !videoTrack.jitsiTrack) {
return;
}

View File

@@ -1,7 +1,7 @@
import NetInfo from '@react-native-community/netinfo';
import type { NetInfoState, NetInfoSubscription } from '@react-native-community/netinfo';
// eslint-disable-next-line lines-around-comment
// @ts-expect-error
// @ts-ignore
import EventEmitter from 'events';
import { ONLINE_STATE_CHANGED_EVENT } from './events';
@@ -15,7 +15,7 @@ export default class NetworkInfoService extends EventEmitter {
/**
* Stores the native subscription for future cleanup.
*/
_subscription?: NetInfoSubscription;
_subscription: NetInfoSubscription;
/**
* Converts library's structure to {@link NetworkInfo} used by jitsi-meet.
@@ -26,8 +26,10 @@ export default class NetworkInfoService extends EventEmitter {
*/
static _convertNetInfoState(netInfoState: NetInfoState): NetworkInfo {
return {
isOnline: Boolean(netInfoState.isInternetReachable),
// @ts-ignore
isOnline: netInfoState.isInternetReachable,
// @ts-ignore
details: netInfoState.details,
networkType: netInfoState.type
};
@@ -49,7 +51,8 @@ export default class NetworkInfoService extends EventEmitter {
*/
start() {
this._subscription = NetInfo.addEventListener(netInfoState => {
super.emit(ONLINE_STATE_CHANGED_EVENT, NetworkInfoService._convertNetInfoState(netInfoState));
// @ts-ignore
this.emit(ONLINE_STATE_CHANGED_EVENT, NetworkInfoService._convertNetInfoState(netInfoState));
});
}
@@ -61,6 +64,8 @@ export default class NetworkInfoService extends EventEmitter {
stop() {
if (this._subscription) {
this._subscription();
// @ts-ignore
this._subscription = undefined;
}
}

View File

@@ -16,13 +16,13 @@ export type NetworkInfo = {
* If {@link networkType} is {@link NetInfoStateType.cellular} then it may provide the info about the type of
* cellular network.
*/
cellularGeneration?: NetInfoCellularGeneration | null;
cellularGeneration?: NetInfoCellularGeneration;
/**
* Indicates whether or not the connection is expensive.
*/
isConnectionExpensive?: boolean;
} | null;
};
/**
* Tells whether or not the internet is reachable.

View File

@@ -1,4 +1,4 @@
// @ts-expect-error
// @ts-ignore
import { getGravatarURL } from '@jitsi/js-utils/avatar';
import { IReduxState, IStore } from '../../app/types';
@@ -200,7 +200,7 @@ export function getVirtualScreenshareParticipantByOwnerId(stateful: IStateful, i
* @returns {string}
*/
export function getNormalizedDisplayName(name: string) {
if (!name?.trim()) {
if (!name || !name.trim()) {
return undefined;
}

View File

@@ -6,7 +6,6 @@ import { IReduxState } from '../../../app/types';
import DialogPortal from '../../../toolbox/components/web/DialogPortal';
import Drawer from '../../../toolbox/components/web/Drawer';
import JitsiPortal from '../../../toolbox/components/web/JitsiPortal';
import { isElementInTheViewport } from '../../ui/functions.web';
import { getContextMenuStyle } from '../functions.web';
/**
@@ -259,16 +258,7 @@ class Popover extends Component<IProps, IState> {
'aria-labelledby': headingId,
'aria-label': !headingId && headingLabel ? headingLabel : undefined
}}
returnFocus = {
// If we return the focus to an element outside the viewport the page will scroll to
// this element which in our case is undesirable and the element is outside of the
// viewport on purpose (to be hidden). For example if we return the focus to the toolbox
// when it is hidden the whole page will move up in order to show the toolbox. This is
// usually followed up with displaying the toolbox (because now it is on focus) but
// because of the animation the whole scenario looks like jumping large video.
isElementInTheViewport
}>
returnFocus = { true }>
{this._renderContent()}
</ReactFocusLock>
</DialogPortal>
@@ -314,8 +304,7 @@ class Popover extends Component<IProps, IState> {
&& !this.props.overflowDrawer
&& this._contextMenuRef
&& this._contextMenuRef.contains
&& !this._contextMenuRef.contains(event.target as Node)
&& !this._containerRef?.current?.contains(event.target as Node)) {
&& !this._contextMenuRef.contains(event.target as Node)) {
this._onHideDialog();
}
}

View File

@@ -1,9 +0,0 @@
/**
* Type for setting the user's consent for unsafe room joining.
*
* {
* type: SET_UNSAFE_ROOM_CONSENT,
* consent: boolean
* }
*/
export const SET_UNSAFE_ROOM_CONSENT = 'SET_UNSAFE_ROOM_CONSENT'

View File

@@ -1,17 +0,0 @@
import { SET_UNSAFE_ROOM_CONSENT } from './actionTypes';
/**
* Sets the consent of the user for joining the unsafe room.
*
* @param {boolean} consent - The user's consent.
* @returns {{
* type: SET_UNSAFE_ROOM_CONSENT,
* consent: boolean
* }}
*/
export function setUnsafeRoomConsent(consent: boolean) {
return {
type: SET_UNSAFE_ROOM_CONSENT,
consent
};
}

View File

@@ -12,8 +12,9 @@ import { getToolbarButtons, isToolbarButtonEnabled } from '../../../config/funct
import { withPixelLineHeight } from '../../../styles/functions.web';
import ConnectionStatus from './ConnectionStatus';
// eslint-disable-next-line lines-around-comment
// @ts-ignore
import Preview from './Preview';
import UnsafeRoomWarning from './UnsafeRoomWarning';
interface IProps {
@@ -57,11 +58,6 @@ interface IProps {
*/
showDeviceStatus: boolean;
/**
* If should show unsafe room warning when joining.
*/
showUnsafeRoomWarning?: boolean;
/**
* The 'Skip prejoin' button to be rendered (if any).
*/
@@ -167,7 +163,6 @@ const PreMeetingScreen = ({
children,
className,
showDeviceStatus,
showUnsafeRoomWarning,
skipPrejoinButton,
title,
videoMuted,
@@ -198,7 +193,6 @@ const PreMeetingScreen = ({
{children}
{_buttons.length && <Toolbox toolbarButtons = { _buttons } />}
{skipPrejoinButton}
{showUnsafeRoomWarning && <UnsafeRoomWarning />}
{showDeviceStatus && <DeviceStatus />}
</div>
</div>

View File

@@ -1,54 +0,0 @@
import React, { useCallback } from 'react';
import { useTranslation } from 'react-i18next';
import { useDispatch, useSelector } from 'react-redux';
import { makeStyles } from 'tss-react/mui';
import { IReduxState } from '../../../../app/types';
import { withPixelLineHeight } from '../../../styles/functions.web';
import Checkbox from '../../../ui/components/web/Checkbox';
import getUnsafeRoomText from '../../../util/getUnsafeRoomText.web';
import { setUnsafeRoomConsent } from '../../actions.web';
const useStyles = makeStyles()(theme => {
return {
warning: {
backgroundColor: theme.palette.warning01,
color: theme.palette.text04,
...withPixelLineHeight(theme.typography.bodyShortRegular),
padding: theme.spacing(3),
borderRadius: theme.shape.borderRadius,
marginBottom: theme.spacing(3)
},
consent: {
padding: `0 ${theme.spacing(3)}`,
'@media (max-width: 720px)': {
marginBottom: theme.spacing(3)
}
}
};
});
const UnsafeRoomWarning = () => {
const { t } = useTranslation();
const { classes } = useStyles();
const dispatch = useDispatch();
const { unsafeRoomConsent } = useSelector((state: IReduxState) => state['features/base/premeeting']);
const toggleConsent = useCallback(
() => dispatch(setUnsafeRoomConsent(!unsafeRoomConsent))
, [ unsafeRoomConsent, dispatch ]);
return (
<>
<div className = { classes.warning }>
{getUnsafeRoomText(t, 'prejoin')}
</div>
<Checkbox
checked = { unsafeRoomConsent }
className = { classes.consent }
label = { t('prejoin.unsafeRoomConsent') }
onChange = { toggleConsent } />
</>
);
};
export default UnsafeRoomWarning;

View File

@@ -1,33 +0,0 @@
import ReducerRegistry from '../redux/ReducerRegistry';
import { SET_UNSAFE_ROOM_CONSENT } from './actionTypes';
import { IPreMeetingState } from './types';
const DEFAULT_STATE: IPreMeetingState = {
unsafeRoomConsent: false
};
/**
* Listen for actions which changes the state of known and used devices.
*
* @param {IDevicesState} state - The Redux state of the feature features/base/devices.
* @param {Object} action - Action object.
* @param {string} action.type - Type of action.
* @returns {IPreMeetingState}
*/
ReducerRegistry.register<IPreMeetingState>(
'features/base/premeeting',
(state = DEFAULT_STATE, action): IPreMeetingState => {
switch (action.type) {
case SET_UNSAFE_ROOM_CONSENT: {
return {
...state,
unsafeRoomConsent: action.consent
};
}
default:
return state;
}
});

View File

@@ -1,3 +0,0 @@
export interface IPreMeetingState {
unsafeRoomConsent?: boolean;
}

View File

@@ -1,2 +1,5 @@
/* eslint-disable lines-around-comment */
// @ts-ignore
export { default as Container } from './native/Container';
// @ts-ignore
export { default as Text } from './native/Text';

View File

@@ -42,7 +42,7 @@ interface IProps {
/**
* Style of the animated view.
*/
style?: StyleType;
style: StyleType;
}
/**

View File

@@ -1,6 +1,6 @@
import React, { Component } from 'react';
import { StyleType, getFixedPlatformStyle } from '../../../styles/functions.web';
import { getFixedPlatformStyle } from '../../../styles/functions.web';
/**
* Implements a React/Web {@link Component} for displaying text similar to React
@@ -16,8 +16,8 @@ export default class Text extends Component<React.HTMLProps<HTMLSpanElement>> {
* @returns {ReactElement}
*/
render() {
// eslint-disable-next-line react/prop-types
const _style = getFixedPlatformStyle(this.props.style as StyleType);
// @ts-ignore
const _style = getFixedPlatformStyle(this.props.style);
return React.createElement('span', {
...this.props,

View File

@@ -1,7 +1,7 @@
// @ts-expect-error
// @ts-ignore
import Bourne from '@hapi/bourne';
// eslint-disable-next-line lines-around-comment
// @ts-expect-error
// @ts-ignore
import { jitsiLocalStorage } from '@jitsi/js-utils';
import md5 from 'js-md5';

View File

@@ -1,3 +1,5 @@
/* eslint-disable lines-around-comment */
import { connect } from 'react-redux';
import { IReduxState } from '../../../../app/types';
@@ -5,7 +7,9 @@ import { translate } from '../../../../base/i18n/functions';
import { IconGear } from '../../../../base/icons/svg';
import AbstractButton, { IProps as AbstractButtonProps } from '../../../../base/toolbox/components/AbstractButton';
import { navigate }
// @ts-ignore
from '../../../../mobile/navigation/components/conference/ConferenceNavigationContainerRef';
// @ts-ignore
import { screen } from '../../../../mobile/navigation/routes';
import { SETTINGS_ENABLED } from '../../../flags/constants';
import { getFeatureFlag } from '../../../flags/functions';

View File

@@ -1,4 +1,4 @@
// @ts-expect-error
// @ts-ignore
import { jitsiLocalStorage } from '@jitsi/js-utils';
import _ from 'lodash';

View File

@@ -1,7 +1,6 @@
import React, { Component, Fragment } from 'react';
import { connect } from 'react-redux';
import { IReduxState } from '../../../app/types';
import statsEmitter from '../../../connection-indicator/statsEmitter';
import { getLocalParticipant } from '../../participants/functions';
import { isTestModeEnabled } from '../functions';
@@ -11,7 +10,7 @@ import TestHint from './TestHint';
/**
* Defines the TestConnectionInfo's properties.
*/
interface IProps {
type Props = {
/**
* The JitsiConference's connection state. It's the lib-jitsi-meet's event
@@ -21,30 +20,30 @@ interface IProps {
* 'conference.connectionInterrupted'
* 'conference.connectionRestored'.
*/
_conferenceConnectionState: string;
_conferenceConnectionState: string,
/**
* This will be a boolean converted to a string. The value will be 'true'
* once the conference is joined (the XMPP MUC room to be specific).
*/
_conferenceJoinedState: string;
_conferenceJoinedState: string,
/**
* The local participant's ID. Required to be able to observe the local RTP
* stats.
*/
_localUserId: string;
_localUserId: string,
/**
* The local participant's role.
*/
_localUserRole: string;
_localUserRole: string,
/**
* Indicates whether or not the test mode is currently on. Otherwise the
* TestConnectionInfo component will not render.
*/
_testMode: boolean;
_testMode: boolean
}
/**
@@ -65,15 +64,15 @@ type State = {
/**
* The local download RTP bitrate.
*/
download: number;
download: number,
/**
* The local upload RTP bitrate.
*/
upload: number;
};
};
};
upload: number
}
}
}
/**
* The component will expose some of the app state to the jitsi-meet-torture
@@ -82,7 +81,8 @@ type State = {
* this information, but there's no such option on React Native(maybe that's
* a good thing).
*/
class TestConnectionInfo extends Component<IProps, State> {
class TestConnectionInfo extends Component<Props, State> {
_onStatsUpdated: Object => void;
/**
* Initializes new <tt>TestConnectionInfo</tt> instance.
@@ -90,7 +90,7 @@ class TestConnectionInfo extends Component<IProps, State> {
* @param {Object} props - The read-only properties with which the new
* instance is to be initialized.
*/
constructor(props: IProps) {
constructor(props: Props) {
super(props);
this._onStatsUpdated = this._onStatsUpdated.bind(this);
@@ -114,8 +114,7 @@ class TestConnectionInfo extends Component<IProps, State> {
* @returns {void}
* @private
*/
_onStatsUpdated(stats = { bitrate: { download: undefined,
upload: undefined } }) {
_onStatsUpdated(stats = {}) {
this.setState({
stats: {
bitrate: {
@@ -144,7 +143,7 @@ class TestConnectionInfo extends Component<IProps, State> {
* @inheritdoc
* returns {void}
*/
componentDidUpdate(prevProps: IProps) {
componentDidUpdate(prevProps: Props) {
if (prevProps._localUserId !== this.props._localUserId) {
statsEmitter.unsubscribeToClientStats(
prevProps._localUserId, this._onStatsUpdated);
@@ -176,7 +175,7 @@ class TestConnectionInfo extends Component<IProps, State> {
}
return (
<Fragment>
<Fragment accessible = { false } >
<TestHint
id = 'org.jitsi.meet.conference.connectionState'
value = { this.props._conferenceConnectionState } />
@@ -185,7 +184,7 @@ class TestConnectionInfo extends Component<IProps, State> {
value = { this.props._conferenceJoinedState } />
<TestHint
id = 'org.jitsi.meet.conference.grantModeratorAvailable'
value = { 'true' } />
value = { true } />
<TestHint
id = 'org.jitsi.meet.conference.localParticipantRole'
value = { this.props._localUserRole } />
@@ -203,9 +202,9 @@ class TestConnectionInfo extends Component<IProps, State> {
*
* @param {Object} state - The Redux state.
* @private
* @returns {IProps}
* @returns {Props}
*/
function _mapStateToProps(state: IReduxState) {
function _mapStateToProps(state) {
const conferenceJoined
= Boolean(state['features/base/conference'].conference);
const localParticipant = getLocalParticipant(state);
@@ -213,8 +212,8 @@ function _mapStateToProps(state: IReduxState) {
return {
_conferenceConnectionState: state['features/testing'].connectionState,
_conferenceJoinedState: conferenceJoined.toString(),
_localUserId: localParticipant?.id ?? '',
_localUserRole: localParticipant?.role ?? '',
_localUserId: localParticipant?.id,
_localUserRole: localParticipant?.role,
_testMode: isTestModeEnabled(state)
};
}

View File

@@ -1,3 +0,0 @@
import { Component } from 'react';
export default Component;

View File

@@ -43,7 +43,7 @@ export default class ToolboxItem extends AbstractToolboxItem<IProps> {
* @returns {void}
*/
_onKeyPress(event?: React.KeyboardEvent) {
if (event?.key === 'Enter') {
if (event?.key === 'Enter' || event?.key === ' ') {
event.preventDefault();
this.props.onClick();
}
@@ -87,16 +87,14 @@ export default class ToolboxItem extends AbstractToolboxItem<IProps> {
const useTooltip = this.tooltip && this.tooltip.length > 0;
if (contextMenu) {
return (
<ContextMenuItem
accessibilityLabel = { this.accessibilityLabel }
disabled = { disabled }
icon = { icon }
onClick = { onClick }
onKeyDown = { onKeyDown }
onKeyPress = { this._onKeyPress }
text = { this.label } />
);
return (<ContextMenuItem
accessibilityLabel = { this.accessibilityLabel }
disabled = { disabled }
icon = { icon }
onClick = { onClick }
onKeyDown = { onKeyDown }
onKeyPress = { this._onKeyPress }
text = { this.label } />);
}
let children = (
<Fragment>

View File

@@ -33,17 +33,17 @@ interface IProps {
/**
* Icon of the button.
*/
icon?: Function;
icon: Function;
/**
* Flag used for disabling the small icon.
*/
iconDisabled?: boolean;
iconDisabled: boolean;
/**
* The ID of the icon button.
*/
iconId?: string;
iconId: string;
/**
* Popover close callback.
@@ -65,11 +65,6 @@ interface IProps {
*/
styles?: Object;
/**
* Whether the trigger for open/ close should be click or hover.
*/
trigger?: 'hover' | 'click';
/**
* Whether or not the popover is visible.
*/
@@ -82,7 +77,7 @@ interface IProps {
* @param {Object} props - Component's props.
* @returns {ReactElement}
*/
export default function ToolboxButtonWithPopup(props: IProps) {
export default function ToolboxButtonWithIconPopup(props: IProps) {
const {
ariaControls,
ariaExpanded,
@@ -96,29 +91,9 @@ export default function ToolboxButtonWithPopup(props: IProps) {
onPopoverOpen,
popoverContent,
styles,
trigger,
visible
} = props;
if (!icon) {
return (
<div
className = 'settings-button-container'
style = { styles }>
<Popover
content = { popoverContent }
headingLabel = { ariaLabel }
onPopoverClose = { onPopoverClose }
onPopoverOpen = { onPopoverOpen }
position = 'top'
trigger = { trigger }
visible = { visible }>
{children}
</Popover>
</div>
);
}
const iconProps: any = {};
if (iconDisabled) {

View File

@@ -1,5 +1,3 @@
import { ReactElement } from 'react';
import { HIDE_TOOLTIP, SHOW_TOOLTIP } from './actionTypes';
/**
@@ -9,7 +7,7 @@ import { HIDE_TOOLTIP, SHOW_TOOLTIP } from './actionTypes';
* Used as unique identifier for tooltip.
* @returns {Object}
*/
export function showTooltip(content: string | ReactElement) {
export function showTooltip(content: string) {
return {
type: SHOW_TOOLTIP,
content
@@ -23,7 +21,7 @@ export function showTooltip(content: string | ReactElement) {
* Used as unique identifier for tooltip.
* @returns {Object}
*/
export function hideTooltip(content: string | ReactElement) {
export function hideTooltip(content: string) {
return {
type: HIDE_TOOLTIP,
content

View File

@@ -16,7 +16,7 @@ const ANIMATION_DURATION = 0.2;
interface IProps {
children: ReactElement;
containerClassName?: string;
content: string | ReactElement;
content: string;
position?: TOOLTIP_POSITION;
}

View File

@@ -1,4 +1,6 @@
import { IReduxState, IStore } from '../../app/types';
// eslint-disable-next-line lines-around-comment
// @ts-ignore
import { setPictureInPictureEnabled } from '../../mobile/picture-in-picture/functions';
import { showNotification } from '../../notifications/actions';
import { NOTIFICATION_TIMEOUT_TYPE } from '../../notifications/constants';

View File

@@ -10,6 +10,8 @@ import { setScreenAudioShareState, setScreenshareAudioTrack } from '../../screen
import { isAudioOnlySharing, isScreenVideoShared } from '../../screen-share/functions';
import { toggleScreenshotCaptureSummary } from '../../screenshot-capture/actions';
import { isScreenshotCaptureEnabled } from '../../screenshot-capture/functions';
// eslint-disable-next-line lines-around-comment
// @ts-ignore
import { AudioMixerEffect } from '../../stream-effects/audio-mixer/AudioMixerEffect';
import { getCurrentConference } from '../conference/functions';
import { JitsiTrackErrors, JitsiTrackEvents } from '../lib-jitsi-meet';

View File

@@ -1,10 +1,8 @@
import { IStore } from '../../app/types';
import { IStateful } from '../app/types';
import { isMobileBrowser } from '../environment/utils';
import JitsiMeetJS, { JitsiTrackErrors, browser } from '../lib-jitsi-meet';
import JitsiMeetJS from '../lib-jitsi-meet';
import { setAudioMuted } from '../media/actions';
import { MEDIA_TYPE } from '../media/constants';
import { getStartWithAudioMuted } from '../media/functions';
import { toState } from '../redux/functions';
import {
getUserSelectedCameraDeviceId,
@@ -96,10 +94,10 @@ export function createLocalTracksF(options: ITrackOptions = {}, store?: IStore)
}
/**
* Returns an object containing a promise which resolves with the created tracks and the errors resulting from that
* process.
* Returns an object containing a promise which resolves with the created tracks &
* the errors resulting from that process.
*
* @returns {Promise<JitsiLocalTrack[]>}
* @returns {Promise<JitsiLocalTrack>}
*
* @todo Refactor to not use APP.
*/
@@ -108,13 +106,7 @@ export function createPrejoinTracks() {
const initialDevices = [ 'audio' ];
const requestedAudio = true;
let requestedVideo = false;
const { startAudioOnly, startWithVideoMuted } = APP.store.getState()['features/base/settings'];
const startWithAudioMuted = getStartWithAudioMuted(APP.store.getState());
// On Electron there is no permission prompt for granting permissions. That's why we don't need to
// spend much time displaying the overlay screen. If GUM is not resolved within 15 seconds it will
// probably never resolve.
const timeout = browser.isElectron() ? 15000 : 60000;
const { startAudioOnly, startWithAudioMuted, startWithVideoMuted } = APP.store.getState()['features/base/settings'];
// Always get a handle on the audio input device so that we have statistics even if the user joins the
// conference muted. Previous implementation would only acquire the handle when the user first unmuted,
@@ -129,66 +121,62 @@ export function createPrejoinTracks() {
requestedVideo = true;
}
let tryCreateLocalTracks: any = Promise.resolve([]);
let tryCreateLocalTracks;
if (requestedAudio || requestedVideo) {
if (!requestedAudio && !requestedVideo) {
// Resolve with no tracks
tryCreateLocalTracks = Promise.resolve([]);
} else {
tryCreateLocalTracks = createLocalTracksF({
devices: initialDevices,
firePermissionPromptIsShownEvent: true,
timeout
firePermissionPromptIsShownEvent: true
}, APP.store)
.catch(async (err: Error) => {
if (err.name === JitsiTrackErrors.TIMEOUT && !browser.isElectron()) {
errors.audioAndVideoError = err;
.catch((err: Error) => {
if (requestedAudio && requestedVideo) {
return [];
}
// Try audio only...
errors.audioAndVideoError = err;
// Retry with separate gUM calls.
const gUMPromises: any = [];
const tracks: any = [];
return (
createLocalTracksF({
devices: [ 'audio' ],
firePermissionPromptIsShownEvent: true
}));
} else if (requestedAudio && !requestedVideo) {
errors.audioOnlyError = err;
if (requestedAudio) {
gUMPromises.push(createLocalTracksF({
devices: [ MEDIA_TYPE.AUDIO ],
firePermissionPromptIsShownEvent: true,
timeout
}));
}
return [];
} else if (requestedVideo && !requestedAudio) {
errors.videoOnlyError = err;
if (requestedVideo) {
gUMPromises.push(createLocalTracksF({
devices: [ MEDIA_TYPE.VIDEO ],
firePermissionPromptIsShownEvent: true,
timeout
}));
}
const results = await Promise.allSettled(gUMPromises);
let errorMsg;
results.forEach((result, idx) => {
if (result.status === 'fulfilled') {
tracks.push(result.value[0]);
} else {
errorMsg = result.reason;
const isAudio = idx === 0;
logger.error(`${isAudio ? 'Audio' : 'Video'} track creation failed with error ${errorMsg}`);
if (isAudio) {
errors.audioOnlyError = errorMsg;
} else {
errors.videoOnlyError = errorMsg;
return [];
}
}
});
logger.error('Should never happen');
})
.catch((err: Error) => {
// Log this just in case...
if (!requestedAudio) {
logger.error('The impossible just happened', err);
}
errors.audioOnlyError = err;
if (errors.audioOnlyError && errors.videoOnlyError) {
errors.audioAndVideoError = errorMsg;
}
// Try video only...
return requestedVideo
? createLocalTracksF({
devices: [ 'video' ],
firePermissionPromptIsShownEvent: true
})
: [];
})
.catch((err: Error) => {
// Log this just in case...
if (!requestedVideo) {
logger.error('The impossible just happened', err);
}
errors.videoOnlyError = err;
return tracks;
});
return [];
});
}
return {

View File

@@ -138,7 +138,7 @@ function _handleNoDataFromSourceErrors(store: IStore, action: any) {
const track = getTrackByJitsiTrack(getState()['features/base/tracks'], action.track.jitsiTrack);
if (!track?.local) {
if (!track || !track.local) {
return;
}

View File

@@ -167,7 +167,6 @@ export const font = {
export const shape = {
borderRadius: 6,
circleRadius: 50,
boxShadow: 'inset 0px -1px 0px rgba(255, 255, 255, 0.15)'
};

View File

@@ -12,12 +12,10 @@ import { commonStyles } from '../constants';
function GlobalStyles() {
const { theme } = useStyles();
return (
<MUIGlobalStyles
styles = {
commonStyles(theme)
} />
);
return (<MUIGlobalStyles
styles = {{
...commonStyles(theme)
}} />);
}
export default GlobalStyles;

View File

@@ -2,6 +2,8 @@ import React from 'react';
import { TouchableRipple } from 'react-native-paper';
import Icon from '../../../icons/components/Icon';
// eslint-disable-next-line lines-around-comment
// @ts-ignore
import styles from '../../../react/components/native/styles';
import { IIconButtonProps } from '../../../react/types';
import { BUTTON_TYPES } from '../../constants.native';

View File

@@ -5,7 +5,6 @@ import { keyframes } from 'tss-react';
import { makeStyles } from 'tss-react/mui';
import { withPixelLineHeight } from '../../../styles/functions.web';
import { isElementInTheViewport } from '../../functions.web';
import { DialogTransitionContext } from './DialogTransition';
@@ -185,16 +184,7 @@ const BaseDialog = ({
onClick = { onBackdropClick } />
<FocusLock
className = { classes.focusLock }
returnFocus = {
// If we return the focus to an element outside the viewport the page will scroll to
// this element which in our case is undesirable and the element is outside of the
// viewport on purpose (to be hidden). For example if we return the focus to the toolbox
// when it is hidden the whole page will move up in order to show the toolbox. This is
// usually followed up with displaying the toolbox (because now it is on focus) but
// because of the animation the whole scenario looks like jumping large video.
isElementInTheViewport
}>
returnFocus = { true }>
<div
aria-describedby = { description }
aria-labelledby = { title ?? t(titleKey ?? '') }

View File

@@ -70,7 +70,7 @@ const useStyles = makeStyles()(theme => {
'& input[type="checkbox"]': {
appearance: 'none',
backgroundColor: 'transparent',
margin: '3px',
margin: 0,
font: 'inherit',
color: theme.palette.icon03,
width: '18px',

View File

@@ -153,7 +153,7 @@ export interface IDialogTab<P> {
labelKey: string;
name: string;
props?: IObject;
propsUpdateFunction?: (tabState: IObject, newProps: P, tabStates?: (IObject | undefined)[]) => P;
propsUpdateFunction?: (tabState: IObject, newProps: P) => P;
submit?: Function;
}
@@ -257,8 +257,7 @@ const DialogWithTabs = ({
if (tabConfiguration.propsUpdateFunction) {
return tabConfiguration.propsUpdateFunction(
currentTabState ?? {},
tabConfiguration.props ?? {},
tabStates);
tabConfiguration.props ?? {});
}
return { ...currentTabState };

View File

@@ -57,28 +57,3 @@ export const findAncestorByClass = (target: HTMLElement | null, cssClass: string
return findAncestorByClass(target.parentElement, cssClass);
};
/**
* Checks if the passed element is visible in the viewport.
*
* @param {Element} element - The element.
* @returns {boolean}
*/
export function isElementInTheViewport(element?: Element): boolean {
if (!element) {
return false;
}
if (!document.body.contains(element)) {
return false;
}
const { innerHeight, innerWidth } = window;
const { bottom, left, right, top } = element.getBoundingClientRect();
if (bottom <= innerHeight && top >= 0 && left >= 0 && right <= innerWidth) {
return true;
}
return false;
}

View File

@@ -1 +0,0 @@
export const SECURITY_URL = 'https://jitsi.org/security/';

View File

@@ -1,31 +0,0 @@
import React from 'react';
import { Text } from 'react-native';
import { IReduxState } from '../../app/types';
import Link from '../react/components/native/Link';
import BaseTheme from '../ui/components/BaseTheme.native';
import { SECURITY_URL } from './contants';
/**
* Gets the unsafe room text for the given context.
*
* @param {IReduxState} state - The redux state.
* @param {Function} t - The translation function.
* @param {'meeting'|'prejoin'|'welcome'} context - The given context of the warining.
* @returns {Text}
*/
export default function getUnsafeRoomText(state: IReduxState, t: Function, context: 'meeting' | 'prejoin' | 'welcome') {
const securityUrl = state['features/base/config'].legalUrls?.security ?? SECURITY_URL;
const link = React.createElement(Link, {
url: securityUrl,
children: 'here',
style: { color: BaseTheme.palette.action01 } });
const options = {
recommendAction: t(`security.unsafeRoomActions.${context}`)
};
return React.createElement(Text, { children: [ t('security.insecureRoomNameWarningNative', options), link, '.' ] });
}

View File

@@ -1,20 +0,0 @@
import { translateToHTML } from '../i18n/functions';
import { SECURITY_URL } from './contants';
/**
* Gets the unsafe room text for the given context.
*
* @param {Function} t - The translation function.
* @param {'meeting'|'prejoin'|'welcome'} context - The given context of the warining.
* @returns {string}
*/
export default function getUnsafeRoomText(t: Function, context: 'meeting' | 'prejoin' | 'welcome') {
const securityUrl = APP.store.getState()['features/base/config'].legalUrls?.security ?? SECURITY_URL;
const options = {
recommendAction: t(`security.unsafeRoomActions.${context}`),
securityUrl
};
return translateToHTML(t, 'security.insecureRoomNameWarningWeb', options);
}

View File

@@ -1,4 +1,4 @@
// @ts-expect-error
// @ts-ignore
import Bourne from '@hapi/bourne';
import { reportError } from './helpers';

View File

@@ -6,6 +6,8 @@ import { IStore } from '../app/types';
import { openDialog } from '../base/dialog/actions';
import { refreshCalendar } from './actions';
// eslint-disable-next-line lines-around-comment
// @ts-ignore
import UpdateCalendarEventDialog from './components/UpdateCalendarEventDialog.native';
import { addLinkToCalendarEntry } from './functions.native';
@@ -38,6 +40,8 @@ export function updateCalendarEvent(eventId: string) {
const roomName = generateRoomWithoutSeparator();
addLinkToCalendarEntry(getState(), eventId, `${defaultUrl}/${roomName}`)
// @ts-ignore
.finally(() => {
dispatch(refreshCalendar(false, false));
});

View File

@@ -40,7 +40,7 @@ function _isDisplayableCalendarEntry(entry: { allDay: boolean; attendees: Object
* @returns {void}
*/
export function _updateCalendarEntries(events: Array<Object>) {
if (!events?.length) {
if (!events || !events.length) {
return;
}

View File

@@ -1,3 +1,4 @@
/* eslint-disable lines-around-comment */
import { IStore } from '../app/types';
import { IStateful } from '../base/app/types';
import { toState } from '../base/redux/functions';
@@ -16,8 +17,11 @@ import {
} from './constants';
import { _updateCalendarEntries } from './functions.web';
import logger from './logger';
// @ts-ignore
import { googleCalendarApi } from './web/googleCalendar';
// @ts-ignore
import { microsoftCalendarApi } from './web/microsoftCalendar';
/* eslint-enable lines-around-comment */
/**
* Determines whether the calendar feature is enabled by the web.

View File

@@ -1,6 +1,6 @@
import { Client } from '@microsoft/microsoft-graph-client';
// eslint-disable-next-line lines-around-comment
// @ts-expect-error
// @ts-ignore
import base64js from 'base64-js';
import { v4 as uuidV4 } from 'uuid';
import { findWindows } from 'windows-iana';

View File

@@ -1,6 +1,10 @@
/* eslint-disable lines-around-comment, max-len */
import { IParticipant } from '../base/participants/types';
import { navigate }
// @ts-ignore
from '../mobile/navigation/components/conference/ConferenceNavigationContainerRef';
// @ts-ignore
import { screen } from '../mobile/navigation/routes';
import { OPEN_CHAT } from './actionTypes';

View File

@@ -1,5 +1,6 @@
/* eslint-disable react/no-multi-comp */
import { Route, useIsFocused } from '@react-navigation/native';
import { useIsFocused } from '@react-navigation/native';
import React, { useEffect } from 'react';
import { connect } from 'react-redux';
@@ -8,7 +9,7 @@ import JitsiScreen from '../../../base/modal/components/JitsiScreen';
import { TabBarLabelCounter } from '../../../mobile/navigation/components/TabBarLabelCounter';
import { closeChat } from '../../actions.native';
import AbstractChat, {
IProps as AbstractProps,
type Props as AbstractProps,
_mapStateToProps
} from '../AbstractChat';
@@ -17,24 +18,24 @@ import MessageContainer from './MessageContainer';
import MessageRecipient from './MessageRecipient';
import styles from './styles';
interface IProps extends AbstractProps {
type Props = AbstractProps & {
/**
* Default prop for navigating between screen components(React Navigation).
*/
navigation: any;
navigation: Object,
/**
* Default prop for navigating between screen components(React Navigation).
*/
route: Route<'', { privateMessageRecipient: { name: string; }; }>;
}
route: Object
};
/**
* Implements a React native component that renders the chat window (modal) of
* the mobile client.
*/
class Chat extends AbstractChat<IProps> {
class Chat extends AbstractChat<Props> {
/**
* Implements React's {@link Component#render()}.
*
@@ -50,16 +51,17 @@ class Chat extends AbstractChat<IProps> {
hasBottomTextInput = { true }
hasTabNavigator = { true }
style = { styles.chatContainer }>
{/* @ts-ignore */}
<MessageContainer messages = { _messages } />
<MessageRecipient privateMessageRecipient = { privateMessageRecipient } />
<ChatInputBar onSend = { this._onSendMessage } />
</JitsiScreen>
);
}
_onSendMessage: (string) => void;
}
export default translate(connect(_mapStateToProps)((props: IProps) => {
export default translate(connect(_mapStateToProps)(props => {
const { _nbUnreadMessages, dispatch, navigation, t } = props;
const unreadMessagesNr = _nbUnreadMessages > 0;
@@ -76,9 +78,7 @@ export default translate(connect(_mapStateToProps)((props: IProps) => {
)
});
return () => {
isFocused && dispatch(closeChat());
};
return () => isFocused && dispatch(closeChat());
}, [ isFocused, _nbUnreadMessages ]);
return (

View File

@@ -1,6 +1,5 @@
import { connect } from 'react-redux';
import { IReduxState } from '../../../app/types';
import { CHAT_ENABLED } from '../../../base/flags/constants';
import { getFeatureFlag } from '../../../base/flags/functions';
import { translate } from '../../../base/i18n/functions';
@@ -11,23 +10,23 @@ import { screen } from '../../../mobile/navigation/routes';
import { getUnreadPollCount } from '../../../polls/functions';
import { getUnreadCount } from '../../functions';
interface IProps extends AbstractButtonProps {
type Props = AbstractButtonProps & {
/**
* True if the polls feature is disabled.
*/
_isPollsDisabled?: boolean;
_isPollsDisabled: boolean,
/**
* The unread message count.
*/
_unreadMessageCount: number;
}
_unreadMessageCount: number
};
/**
* Implements an {@link AbstractButton} to open the chat screen on mobile.
*/
class ChatButton extends AbstractButton<IProps> {
class ChatButton extends AbstractButton<Props, *> {
accessibilityLabel = 'toolbar.accessibilityLabel.chat';
icon = IconMessage;
label = 'toolbar.chat';
@@ -61,9 +60,9 @@ class ChatButton extends AbstractButton<IProps> {
*
* @param {Object} state - The Redux state.
* @param {Object} ownProps - The properties explicitly passed to the component instance.
* @returns {IProps}
* @returns {Props}
*/
function _mapStateToProps(state: IReduxState, ownProps: any) {
function _mapStateToProps(state, ownProps) {
const enabled = getFeatureFlag(state, CHAT_ENABLED, true);
const { disablePolls } = state['features/base/config'];
const { visible = enabled } = ownProps;

View File

@@ -25,6 +25,10 @@ class ChatPrivacyDialog extends AbstractChatPrivacyDialog {
onSubmit = { this._onSendPrivateMessage } />
);
}
_onSendGroupMessage: () => boolean;
_onSendPrivateMessage: () => boolean;
}
export default translate(connect(_mapStateToProps, _mapDispatchToProps)(ChatPrivacyDialog));

View File

@@ -1,65 +1,63 @@
import React from 'react';
import { Text, TouchableHighlight, View, ViewStyle } from 'react-native';
import { Text, TouchableHighlight, View } from 'react-native';
import { connect } from 'react-redux';
import { IReduxState, IStore } from '../../../app/types';
import { translate } from '../../../base/i18n/functions';
import Icon from '../../../base/icons/components/Icon';
import { IconCloseLarge } from '../../../base/icons/svg';
import { ILocalParticipant } from '../../../base/participants/types';
import {
setParams
} from '../../../mobile/navigation/components/conference/ConferenceNavigationContainerRef';
import { setLobbyChatActiveState, setPrivateMessageRecipient } from '../../actions.any';
import AbstractMessageRecipient, {
IProps as AbstractProps
type Props as AbstractProps
} from '../AbstractMessageRecipient';
import styles from './styles';
interface IProps extends AbstractProps {
type Props = AbstractProps & {
/**
* The Redux dispatch function.
*/
dispatch: IStore['dispatch'];
dispatch: Function,
/**
* Is lobby messaging active.
*/
isLobbyChatActive: boolean;
isLobbyChatActive: boolean,
/**
* The participant string for lobby chat messaging.
*/
lobbyMessageRecipient?: {
id: string;
name: string;
} | ILocalParticipant;
lobbyMessageRecipient: Object,
/**
* The participant object set for private messaging.
*/
privateMessageRecipient: { name: string; };
}
privateMessageRecipient: Object,
};
/**
* Class to implement the displaying of the recipient of the next message.
*/
class MessageRecipient extends AbstractMessageRecipient<IProps> {
class MessageRecipient extends AbstractMessageRecipient<Props> {
/**
* Constructor of the component.
*
* @param {IProps} props - The props of the component.
* @param {Props} props - The props of the component.
*/
constructor(props: IProps) {
constructor(props: Props) {
super(props);
this._onResetPrivateMessageRecipient = this._onResetPrivateMessageRecipient.bind(this);
this._onResetLobbyMessageRecipient = this._onResetLobbyMessageRecipient.bind(this);
}
_onResetLobbyMessageRecipient: () => void;
/**
* Resets lobby message recipient from state.
*
@@ -71,6 +69,8 @@ class MessageRecipient extends AbstractMessageRecipient<IProps> {
dispatch(setLobbyChatActiveState(false));
}
_onResetPrivateMessageRecipient: () => void;
/**
* Resets private message recipient from state.
*
@@ -102,10 +102,10 @@ class MessageRecipient extends AbstractMessageRecipient<IProps> {
if (isLobbyChatActive) {
return (
<View style = { styles.lobbyMessageRecipientContainer as ViewStyle }>
<View style = { styles.lobbyMessageRecipientContainer }>
<Text style = { styles.messageRecipientText }>
{ t('chat.lobbyChatMessageTo', {
recipient: lobbyMessageRecipient?.name
recipient: lobbyMessageRecipient.name
}) }
</Text>
<TouchableHighlight
@@ -123,7 +123,7 @@ class MessageRecipient extends AbstractMessageRecipient<IProps> {
}
return (
<View style = { styles.messageRecipientContainer as ViewStyle }>
<View style = { styles.messageRecipientContainer }>
<Text style = { styles.messageRecipientText }>
{ t('chat.messageTo', {
recipient: privateMessageRecipient.name
@@ -145,10 +145,9 @@ class MessageRecipient extends AbstractMessageRecipient<IProps> {
* Maps part of the redux state to the props of this component.
*
* @param {Object} state - The Redux state.
* @param {any} _ownProps - Component's own props.
* @returns {IProps}
* @returns {Props}
*/
function _mapStateToProps(state: IReduxState, _ownProps: any) {
function _mapStateToProps(state) {
const { lobbyMessageRecipient, isLobbyChatActive } = state['features/chat'];
return {

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