Compare commits

...

54 Commits

Author SHA1 Message Date
kerem
4c0c36d233 feat(deps,rn) update React Native to version 0.69.9 (#13241)
* feat(deps,rn) update React Native to version 0.69
2023-05-16 19:12:51 +03:00
Robert Pintilii
59f1ee1e1e ref(TS) Improve TS (#13355) 2023-05-16 12:34:30 +03:00
Jaya Allamsetty
1af90a208e chore(deps) lib-jitsi-meet@latest
https://github.com/jitsi/lib-jitsi-meet/compare/v1633.0.0+6eb33daf...v1635.0.0+152fdb21
2023-05-15 21:44:33 -04:00
damencho
22c6b72a75 fix: Do not allow visitors while lobby is enabled. 2023-05-15 16:19:16 -05:00
Дамян Минков
d8c7f8de81 feat: Jaas example that uses local jitsi-meet (#13350)
* feat: Adds an example to convert a deployment to use jaas.

* squash: Generates the daily asap token with expiration of 1 day.

The default is 1 hour.

* squash: Use local deployment UI with jaas, not 8x8.vc one.

- We load config.js from 8x8.vc with the tenant, to allow release pinning to work.

- We sed the vpass_cookie in the custom nginx conf as variables are not allowed in location matching.

- The jaas-vars need to be global as it will overwrite config.js location and index html.

* squash: Enables e2ee for the meetings.

* squash: Bump node version check.

* squash: Fix filename.

* squash: Updates the readme.

* squash: Checks whether node is installed.

* squash: Fixes initial configuration.

The jaas-vars is required to reload nginx, done by update-asap-daily script.

* squash: More fixes of misspelled config file.

* squash: Fixes serving the pub key.
2023-05-15 14:52:27 -05:00
José Luís Andrade
f76122f0b0 fix(lang) update Portuguese translation 2023-05-15 11:08:37 +02:00
Robert Pintilii
63927db9e4 ref(self-view) Move Hide self view to General tab (#13339) 2023-05-12 16:40:36 +03:00
Robert Pintilii
6b28af8329 ref(flow) Remove flow (#13343)
Remove flow-related packages
Remove type annotations from JS files
2023-05-12 16:39:18 +03:00
Jaya Allamsetty
ccebccf8e6 chore(deps) lib-jitsi-meet@latest
https://github.com/jitsi/lib-jitsi-meet/compare/v1632.0.0+e4966db9...v1633.0.0+6eb33daf
2023-05-12 08:43:08 -04:00
Gabriel Borlea
279a4efb83 fix(prejoin): avoid bypass unsafe consent when enter is pressed in name input (#13344) 2023-05-12 15:13:35 +03:00
Mihaela Dumitru
ceac1ab25f chore(deps) bump excalidraw version 2023-05-12 14:00:47 +02:00
Robert Pintilii
2ba6bcf172 chore(deps) Upgrade eslint (#13341)
Upgrade eslint related packages
Fix new errors
2023-05-12 13:24:50 +03:00
Robert Pintilii
ae0669fa07 ref(TS) Convert always-on-top to TS (#13332) 2023-05-12 10:28:50 +03:00
Saúl Ibarra Corretgé
71627f97f7 fix(android) set MainActivity launch mode to singleInstance 2023-05-11 13:49:22 +02:00
Jaya Allamsetty
e35338b73d chore(deps) lib-jitsi-meet@latest
https://github.com/jitsi/lib-jitsi-meet/compare/v1631.0.0+f0dd4039...v1632.0.0+e4966db9
2023-05-09 15:31:28 -04:00
Gabriel Borlea
a112d38943 feat(prejoin) add unsafe room name warning 2023-05-09 20:04:58 +02:00
Horatiu Muresan
b705c63a65 fix(local-recording) Enable local rec for non-moderators (#13334) 2023-05-09 17:52:20 +03:00
Saúl Ibarra Corretgé
b4c8f7d097 feat(external-api) optional sandbox
Add optional "sandbox" option to the external API which will be applied
to the iframe.
2023-05-09 15:18:28 +02:00
Robert Pintilii
74bac9806f fix(virtual-background) Use correct video device (#13329)
In the case when the user selects a new video device then goes to Virtual Background without saving first, use the newly selected device for the virtual background preview instead of the saved one
2023-05-09 15:39:51 +03:00
Robert Pintilii
65248d7d29 chore(deps) Upgrade TS (#13331)
Upgrade typescript related packages
Fix new errors and warnings
2023-05-09 15:39:31 +03:00
Robert Pintilii
dc037bc8dd ref(TS) Convert some native components to TS (#13307) 2023-05-09 12:10:46 +03:00
Robert Pintilii
a22db037c7 ref(TS) Improve TS (#13282)
Remove unnecessary @ts-ignores
Use @ts-expect-error instead of @ts-ignore for external dependencies
2023-05-09 11:05:11 +03:00
Horatiu Muresan
44cc0f7e9a fix(toolbar-buttons) Hide rec and livestream buttons for non-moderators (#13328) 2023-05-08 19:17:01 +03:00
Calinteodor
eafb337cd1 feat(video-menu): native track volume control updates (#13305)
* feat(video-menu/native): added volume slider control for remote participants
2023-05-08 16:33:45 +03:00
damencho
9b3be66287 feat: Updates unsupported desktop to tsx.
Easier to apply branding.
2023-05-05 12:44:33 -05:00
Jaya Allamsetty
1a10a00f74 chore(deps) lib-jitsi-meet@latest
https://github.com/jitsi/lib-jitsi-meet/compare/v1629.0.0+1714bf07...v1631.0.0+f0dd4039
2023-05-05 11:05:50 -04:00
George Politis
a196bc27b8 Update RTCStats.ts
Addresses an issue where the client is sending malformed stats messages to the server.

Introduced in 78ce68160a .
2023-05-05 11:30:04 +02:00
damencho
ac8e4d9828 fix: Fixes notifying vnodes to destroy rooms.
It may happen we receive the disconnect iq after the main room is already destroyed. We now send disconnect on destroy to all vnodes or when receiving disconnect iq per vnode.
2023-05-04 15:10:02 -05:00
damencho
a6ade336b7 fix: Fix measure messages.
This just counts messages and should not block processing messages. Breaks visitor messages coming from s2s and no session params are available.
2023-05-04 15:09:52 -05:00
Hristo Terezov
350443ad34 fix(large-video): Attempt to fix jumping.
When the toolbox is hidden and due to a ReactFocusLock instance the
focus is returned to the toolbox the whole page scrolls to the toolbox
which is positioned outside of the viewport in the bottom.
Then when the animation for displaying the toolbox is started the
scenario looks like the large video is jumping.
Now we don't return the focus from ReactFocusLock to elements which are
not part of the viewport.
2023-05-03 17:31:54 -05:00
Jaya Allamsetty
4c37ef7a2c ref(conference) Simplify track creation. (#13209)
* ref(conference) Simplify track creation.
If gUM fails, we do not have to retry gUM with mic only and camera only constraints. gUM has come a long way and this is not needed anymore.

* ref(conference) Filter tracks that are added to conference.

* squash: Address review comments

* fix(prejoin): Display the exact gUM error in prejoin.

* squash: Address review comments
2023-05-03 18:16:48 -04:00
damencho
3eedc2a49d fix: Restore old config for promoted visitors.
In case a visitor is promoted to main room and want to join an empty breakout room we want to send conference iq to jicofo.
2023-05-03 11:04:40 -05:00
FIKRAT HUSEYNKHANOV
aaeb1a90e5 feat: add toggleWhiteboard to Jitsi API (#13292)
* add toggleWhiteboard to Jitsi API

* eslint recommendations applied

* Prevent to send whiteboard status change notifications for mobile

* Fix code style errors (eslint)

* Requested changes (by mihhu) have been made.

---------

Co-authored-by: Fikret Huseynkhanov <fikret.huseynkhanov@simbrella.com>
2023-05-03 09:52:44 -05:00
Saúl Ibarra Corretgé
ed89f9af20 feat(android) add support for Hearing Aid devices 2023-05-03 09:09:10 +02:00
Saúl Ibarra Corretgé
863ad0b0e6 fix(ios) bump WebRTC version to fix crash
Fix a crash in Metal rendering: 0c9cc42025
2023-05-02 23:12:56 +02:00
Дамян Минков
e2d701a8cc feat: Audio output settings in visitor mode. (#13315)
* feat: Audio output settings in visitor mode.

* squash: Fix lint errors.
2023-05-02 16:06:19 -05:00
damencho
2710273069 feat: Sets meeting id using the connect method. 2023-05-02 12:40:08 -05:00
damencho
07af18e284 fix: Handles disconnect coming from jicofo to destroy visitor rooms.
Drops destroy room for no main participants from vnode. In case of breakout rooms we can end up with nobody in the main room for some time, till they are back from the breakout rooms.
2023-05-02 12:40:08 -05:00
damencho
519e37f567 fix: Drops console warn for logger. 2023-05-02 12:40:08 -05:00
damencho
7cf61eb776 fix: Disables p2p for visitors as it doesn't make sense.
The usecase is breakout rooms when main room is empty, but then one of the participants come back.
2023-05-02 12:40:08 -05:00
Jaya Allamsetty
f81446909c chore(deps) lib-jitsi-meet@latest
https://github.com/jitsi/lib-jitsi-meet/compare/v1626.0.0+a41aa571...v1629.0.0+1714bf07
2023-05-02 10:01:10 -04:00
damencho
b4115593c0 fix: Fix xmldom version to be used. 2023-05-02 08:29:10 -05:00
Hristo Terezov
f8bd8b616e feat(reactions): New button for web. 2023-05-02 08:20:35 -05:00
Hristo Terezov
be55ccd6f4 fix(main-Toolbox): Display the correct buttons.
If some of the buttons from the main toolbar are disabled we were
displaying buttons from the overflow menu in their place.
2023-05-02 08:20:35 -05:00
Horatiu Muresan
e7db18bd80 fix(dial-in) Place PIN on a new line (#13309) 2023-05-02 16:02:51 +03:00
Robert Pintilii
dff41e0fcb fix(chat) Fix horizontal scroll (#13308) 2023-05-02 15:53:30 +03:00
damencho
43be4324af fix: Fix room locking without visitors. 2023-05-02 06:25:37 -05:00
Saúl Ibarra Corretgé
c33baf4c96 fix(ios) avoid rejecting builds in progress in TestFlight 2023-05-02 10:21:30 +02:00
Saúl Ibarra Corretgé
f95a356025 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-02 10:19:19 +02:00
Robert Pintilii
1ba7765898 ref(TS) Convert some native components to TS (#13281)
Remove some @ts-ignores
2023-05-02 11:09:38 +03:00
Robert Pintilii
0346fca434 fix(checkbox) Fix misalign when label has multiple lines (#13304) 2023-05-02 10:23:04 +03:00
Robert Pintilii
d267b2499d fix(chat) Fix name overflows chat bubble (#13303)
Revert color change of scroll corner
2023-05-02 10:22:49 +03:00
Дамян Минков
e3e5f1fbfa feat(visitors): Handles locked rooms for visitors. (#13296)
* feat(visitors): Handles locked rooms for visitors.

* squash: Handle locked room password on promotion.

* squash: quotes.

* squash: Renames main_domain to local_domain.

* squash: Renames fmuc_main_domain to main_domain.

Adds required config to point to the main virtual host of the main prosody. There are cases when the first visitor tries to join and there are not main participants as they are in the queue waiting for the vnode connect message and we cannot get dynamically the main domain.

* squash: Fix check for main_domain config.
2023-05-01 17:16:16 -05:00
robertpin
4697192b43 fix(keyboard-a11y) Remove space from click trigger 2023-05-01 16:50:23 +02:00
404 changed files with 6098 additions and 5179 deletions

View File

@@ -1,6 +1,8 @@
# The build artifacts of the jitsi-meet project.
build/*
doc/*
# Third-party source code which we (1) do not want to modify or (2) try to
# modify as little as possible.
libs/*

5
.gitignore vendored
View File

@@ -61,8 +61,9 @@ buck-out/
# fastlane
#
*/fastlane/report.xml
*/fastlane/Preview.html
**/fastlane/report.xml
**/fastlane/Preview.html
**/fastlane/test_output
# Build artifacts
*.jsbundle

View File

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

View File

@@ -10,16 +10,17 @@ buildscript {
mavenCentral()
}
dependencies {
classpath 'com.android.tools.build:gradle:7.0.4'
classpath 'com.android.tools.build:gradle:7.1.1'
classpath 'com.google.gms:google-services:4.3.14'
classpath 'com.google.firebase:firebase-crashlytics-gradle:2.9.2'
}
}
ext {
kotlinVersion = "1.5.32"
buildToolsVersion = "31.0.0"
compileSdkVersion = 32
minSdkVersion = 23
minSdkVersion = 24
targetSdkVersion = 32
supportLibVersion = "28.0.0"

View File

@@ -33,7 +33,7 @@ if [[ $MVN_HTTP == 1 ]]; then
deploy:deploy-file \
-Durl=${MVN_REPO} \
-DrepositoryId=${MVN_REPO_ID} \
-Dfile=react-native-${RN_VERSION}.aar \
-Dfile=react-native-${RN_VERSION}-release.aar \
-Dpackaging=aar \
-DgeneratePom=false \
-DpomFile=react-native-${RN_VERSION}.pom || true
@@ -58,7 +58,7 @@ else
mvn \
deploy:deploy-file \
-Durl=${MVN_REPO} \
-Dfile=react-native-${RN_VERSION}.aar \
-Dfile=react-native-${RN_VERSION}-release.aar \
-Dpackaging=aar \
-DgeneratePom=false \
-DpomFile=react-native-${RN_VERSION}.pom

View File

@@ -45,6 +45,12 @@ 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
@@ -85,6 +91,7 @@ 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;
let tryCreateLocalTracks = Promise.resolve([]);
// 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,76 +600,65 @@ export default {
return [];
});
} else if (!requestedAudio && !requestedVideo) {
// Resolve with no tracks
tryCreateLocalTracks = Promise.resolve([]);
} else {
} else if (requestedAudio || requestedVideo) {
tryCreateLocalTracks = createLocalTracksF({
devices: initialDevices,
timeout,
firePermissionPromptIsShownEvent: true
})
.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;
.catch(async error => {
if (error.name === JitsiTrackErrors.TIMEOUT && !browser.isElectron()) {
errors.audioAndVideoError = error;
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 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.
// 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));
@@ -810,43 +799,51 @@ 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
// when the camera device has switched USB ports.
// 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 -
// 1. When the camera device has switched USB ports.
// 2. When in startSilent mode we want to start with audio muted
await this._initDeviceList();
} catch (error) {
logger.warn('initial device list initialization failed', error);
}
const handleStartAudioMuted = (options, tracks) => {
if (options.startWithAudioMuted) {
// 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()) {
// 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 {
return tracks.filter(track => track.getType() !== MEDIA_TYPE.AUDIO);
localTracks = localTracks.filter(track => track.getType() !== MEDIA_TYPE.AUDIO);
}
}
if (room?.isStartVideoMuted()) {
localTracks = localTracks.filter(track => track.getType() !== MEDIA_TYPE.VIDEO);
}
return tracks;
return localTracks;
};
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;
@@ -859,48 +856,28 @@ export default {
APP.store.dispatch(makePrecallTest(this._getConferenceOptions()));
const { tryCreateLocalTracks, errors } = this.createInitialLocalTracks(initialOptions);
const tracks = await tryCreateLocalTracks;
const localTracks = 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(tracks, errors));
return APP.store.dispatch(initPrejoin(localTracks, errors));
}
logger.debug('Prejoin screen no longer displayed at the time when tracks were created');
this._displayErrorsForCreateInitialLocalTracks(errors);
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);
return this._setLocalAudioVideoStreams(handleInitialTracks(initialOptions, localTracks));
}
const [ tracks, con ] = await this.createInitialLocalTracksAndConnect(roomName, initialOptions);
this._initDeviceList(true);
return this.startConference(con, handleStartAudioMuted(initialOptions, tracks));
return this.startConference(con, handleInitialTracks(initialOptions, tracks));
},
/**
@@ -1073,7 +1050,7 @@ export default {
*/
muteVideo(mute, showUI = true) {
if (this.videoSwitchInProgress) {
console.warn('muteVideo - unable to perform operations while video switch is in progress');
logger.warn('muteVideo - unable to perform operations while video switch is in progress');
return;
}

View File

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

View File

@@ -1,3 +1,6 @@
doc/debian/jitsi-meet/jitsi-meet.example /usr/share/jitsi-meet-web-config/
doc/debian/jitsi-meet/jitsi-meet.example-apache /usr/share/jitsi-meet-web-config/
config.js /usr/share/jitsi-meet-web-config/
doc/jaas/nginx-jaas.conf /usr/share/jitsi-meet-web-config/
doc/jaas/index-jaas.html /usr/share/jitsi-meet-web-config/
doc/jaas/8x8.vc-config.js /usr/share/jitsi-meet-web-config/

View File

@@ -12,3 +12,5 @@ resources/robots.txt /usr/share/jitsi-meet/
resources/*.sh /usr/share/jitsi-meet/scripts/
pwa-worker.js /usr/share/jitsi-meet/
manifest.json /usr/share/jitsi-meet/
doc/jaas/move-to-jaas.sh /usr/share/jitsi-meet/scripts/
doc/jaas/update-asap-daily.sh /usr/share/jitsi-meet/scripts/

View File

@@ -58,6 +58,8 @@ server {
add_header Strict-Transport-Security "max-age=63072000" always;
set $prefix "";
set $custom_index "";
set $config_js_location /etc/jitsi/meet/jitsi-meet.example.com-config.js;
ssl_certificate /etc/jitsi/meet/jitsi-meet.example.com.crt;
ssl_certificate_key /etc/jitsi/meet/jitsi-meet.example.com.key;
@@ -77,8 +79,10 @@ server {
gzip_proxied no-cache no-store private expired auth;
gzip_min_length 512;
include /etc/jitsi/meet/jaas/*.conf;
location = /config.js {
alias /etc/jitsi/meet/jitsi-meet.example.com-config.js;
alias $config_js_location;
}
location = /external_api.js {
@@ -92,6 +96,11 @@ server {
proxy_set_header Host $http_host;
}
location ~ ^/_api/public/(.*)$ {
autoindex off;
alias /etc/jitsi/meet/public/$1;
}
# ensure all static content can always be found first
location ~ ^/(libs|css|static|images|fonts|lang|sounds|.well-known)/(.*)$
{
@@ -142,11 +151,12 @@ server {
#}
location ~ ^/([^/?&:'"]+)$ {
set $roomname "$1";
try_files $uri @root_path;
}
location @root_path {
rewrite ^/(.*)$ / break;
rewrite ^/(.*)$ /$custom_index break;
}
location ~ ^/([^/?&:'"]+)/config.js$
@@ -154,7 +164,7 @@ server {
set $subdomain "$1.";
set $subdir "$1/";
alias /etc/jitsi/meet/jitsi-meet.example.com-config.js;
alias $config_js_location;
}
# Matches /(TENANT)/pwa-worker.js or /(TENANT)/manifest.json to rewrite to / and look for file

View File

@@ -0,0 +1,7 @@
</script>
<script src="https://8x8.vc/<!--# echo var="subdir" default="" -->config.js" onload="{
config.p2p.disabledCodec='VP9';
config.videoQuality.disabledCodec='VP9';
config.e2ee = { externallyManagedKey: true };
}"/>
<script>

22
doc/jaas/README.md Normal file
View File

@@ -0,0 +1,22 @@
## How to switch your deployment to [JaaS](https://jaas.8x8.vc) in one easy step
Note: By default it will have e2ee(end-to-end) encryption enabled that works only on chromium based browsers (Chrome, Edge, ...). If a participant joins from another browser or mobile the e2ee is turned off.
In order to use your deployment with JaaS you first need to login to your [JaaS Developer console](https://jaas.8x8.vc/#/apikeys) and generate a key pair.
Use `Add API key` button and then `Generate API key pair`. Make sure you download the generated private key from:
<img src="generated_key_dialog.png" height="250">
Make sure you transfer this downloaded private key to your server. Copy the key id from:
<img src="api_keys_kid.png" height="200">
Now on your server run the helper script passing the private key file and the key id:
```
sudo /usr/share/jitsi-meet/scripts/move-to-jaas.sh /my/path/test-key.pk <key_id>
```
More information about JaaS Api keys at: https://developer.8x8.com/jaas/docs/jaas-console-api-keys
If you want to adjust the enabled services you can do that in /etc/jits/meet/jaas/nginx-jaas.conf. The part after `proxy_set_body` is the jwt token content that will be used for the client tokens. More info about the JaaS tokens: https://developer.8x8.com/jaas/docs/api-keys-jwt

BIN
doc/jaas/api_keys_kid.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 54 KiB

33
doc/jaas/index-jaas.html Normal file
View File

@@ -0,0 +1,33 @@
<!DOCTYPE html>
<html>
<head>
<script src='external_api.js' async></script>
<style>html, body, #jaas-container { height: 100%; }</style>
<script type="text/javascript">
function getRoomName(pathname) {
const contextRootEndIndex = pathname.lastIndexOf('/');
return pathname.substring(contextRootEndIndex + 1);
}
window.onload = () => {
const jaasJwt = <!--#include virtual="/jaas-jwt" -->;
const api = new JitsiMeetExternalAPI(
window.location.host, {
roomName: `${jaasJwt.tenant}/${getRoomName(window.location.pathname)}`,
parentNode: document.querySelector('#jaas-container'),
jwt: jaasJwt.token,
e2eeKey: jaasJwt.e2eeKey
});
api.addListener('videoConferenceJoined', () => {
if (jaasJwt.e2eeKey) {
console.info('Toggling e2ee on!')
api.executeCommand('toggleE2EE', true);
}
});
}
</script>
</head>
<body>
<div id="jaas-container" />
</body>
</html>

59
doc/jaas/move-to-jaas.sh Executable file
View File

@@ -0,0 +1,59 @@
#!/bin/bash
set -e
PRIVATE_KEY=$1
JAAS_KEY_ID=$2
if [ ! -f "${PRIVATE_KEY}" ] ; then
echo "You need to specify a correct path for the private key as a first argument."
exit 1;
fi
if [[ ! "${JAAS_KEY_ID}" =~ ^vpaas-magic-cookie-[0-9a-z]+/[0-9a-z]+$ ]]; then
echo "Invalid key id passed as a second argument."
exit 2;
fi
command -v node >/dev/null 2>&1 || { echo >&2 "You must install node first, go to https://nodejs.org. Aborting."; exit 4; }
NODE_VER=$(node -v);
NODE_MAJOR_VER=$(echo ${NODE_VER:1} | cut -d. -f1);
if [ "$NODE_MAJOR_VER" -lt "18" ]; then
echo "Please install latest LTS version of node (18+)";
exit 3;
fi
# we need this util for debconf-set-selections
sudo apt install debconf-utils
# Let's pre-set some settings for token-generator
cat << EOF | sudo debconf-set-selections
token-generator token-generator/private-key string ${PRIVATE_KEY}
token-generator token-generator/kid string ${JAAS_KEY_ID}
EOF
apt install token-generator
mkdir -p /etc/jitsi/meet/jaas
VPASS_COOKIE=$(echo -n ${JAAS_KEY_ID}| cut -d/ -f1)
cp /usr/share/jitsi-meet-web-config/nginx-jaas.conf /etc/jitsi/meet/jaas
sed -i "s/jaas_magic_cookie/${VPASS_COOKIE}/g" /etc/jitsi/meet/jaas/nginx-jaas.conf
cp /usr/share/jitsi-meet-web-config/8x8.vc-config.js /etc/jitsi/meet/jaas/
echo "set \$config_js_location /etc/jitsi/meet/jaas/8x8.vc-config.js;" >> /etc/jitsi/meet/jaas/jaas-vars
echo "set \$custom_index index-jaas.html;" >> /etc/jitsi/meet/jaas/jaas-vars
ln -s /usr/share/jitsi-meet-web-config/index-jaas.html /usr/share/jitsi-meet/index-jaas.html
# let's create the daily key now
/usr/share/jitsi-meet/scripts/update-asap-daily.sh
# let's add to cron daily the update of the asap key
if [ -d /etc/cron.daily ]; then
ln -s /usr/share/jitsi-meet/scripts/update-asap-daily.sh /etc/cron.daily/update-jaas-asap.sh
else
echo "No /etc/cron.daily. Please add to your cron jobs to execute as root daily the script: /usr/share/jitsi-meet/scripts/update-asap-daily.sh"
fi

23
doc/jaas/nginx-jaas.conf Normal file
View File

@@ -0,0 +1,23 @@
include /etc/jitsi/meet/jaas/jaas-vars;
location = /jaas-jwt {
include /etc/jitsi/token-generator/daily-key;
ssi on;
proxy_method POST;
proxy_set_header content-type "application/json";
proxy_set_header Accept-Encoding "";
proxy_set_header Authorization "Bearer $jaas_asap_key";
proxy_pass_request_body off;
proxy_set_body '{"sub":"jaas_magic_cookie","context":{"features":{"livestreaming":false,"outbound-call":false,"sip-outbound-call":false,"transcription":false,"recording":false},"user":{"moderator":true}},"room": "$roomname"}';
proxy_pass http://127.0.0.1:8017/generate/client?e2eeKey=true;
}
location @magic_root_path {
rewrite ^/(.*)$ /index.html break;
}
# Anything that didn't match above, and isn't a real file, assume it's a room name and redirect to /
location ~ ^/jaas_magic_cookie/(.*)$ {
set $subdomain "jaas_magic_cookie.";
set $subdir "jaas_magic_cookie/";
try_files $1 @magic_root_path;
}

9
doc/jaas/update-asap-daily.sh Executable file
View File

@@ -0,0 +1,9 @@
JWT_KID=$(cat /etc/jitsi/token-generator/config | grep SYSTEM_ASAP_BASE_URL_MAPPINGS | cut -d= -f2- | jq -r .[].kid)
JWT_DATE=$(echo -n $JWT_KID | cut -d/ -f2-)
JWT_DATE=${JWT_DATE#jwt-}
KEY_FILE=/etc/jitsi/token-generator/daily-key
echo -n "set \$jaas_asap_key " > ${KEY_FILE}
ASAP_KEY=$(ASAP_SIGNING_KEY_FILE=/etc/jitsi/token-generator/asap-${JWT_DATE}.key ASAP_JWT_KID="${JWT_KID}" ASAP_EXPIRES_IN="1 day" node /usr/share/token-generator/jwt.js| tail -n1)
echo -n "${ASAP_KEY};" >> ${KEY_FILE}
service nginx reload

1
globals.d.ts vendored
View File

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

1
globals.native.d.ts vendored
View File

@@ -30,6 +30,7 @@ interface IWindow {
setImmediate: typeof setImmediate;
clearImmediate: typeof clearImmediate;
addEventListener: Function;
removeEventListener: Function;
}
interface INavigator {

View File

@@ -1,11 +1,13 @@
require_relative '../node_modules/react-native/scripts/react_native_pods'
require_relative '../node_modules/@react-native-community/cli-platform-ios/native_modules'
platform :ios, '12.0'
platform :ios, '12.4'
workspace 'jitsi-meet'
install! 'cocoapods', :deterministic_uuids => false
production = ENV["PRODUCTION"] == "1"
target 'JitsiMeet' do
project 'app/app.xcodeproj'
@@ -21,8 +23,10 @@ target 'JitsiMeetSDK' do
#
config = use_native_modules!
flags = get_default_flags()
use_react_native!(
:path => config["reactNativePath"],
:path => config[:reactNativePath],
:production => production,
:hermes_enabled => false,
:fabric_enabled => false,
# An absolute path to your application root.
@@ -81,7 +85,7 @@ post_install do |installer|
end
target.build_configurations.each do |config|
config.build_settings['SUPPORTS_MACCATALYST'] = 'NO'
config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = '12.0'
config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = '12.4'
end
end
end

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.1)
- JitsiWebRTC (111.0.2)
- 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: 9619c1f71cc16eeca76df68aa2d213c6d63274a8
JitsiWebRTC: 80f62908fcf2a1160e0d14b584323fb6e6be630b
libwebp: f62cb61d0a484ba548448a4bd52aabf150ff6eef
nanopb: a0ba3315591a9ae0a16a309ee504766e90db0c96
ObjectiveDropboxOfficial: fe206ce8c0bc49976c249d472db7fdbc53ebbd53

View File

@@ -439,7 +439,7 @@
};
};
buildConfigurationList = 83CBB9FA1A601CBA00E9B192 /* Build configuration list for PBXProject "app" */;
compatibilityVersion = "Xcode 3.2";
compatibilityVersion = "Xcode 12.0";
developmentRegion = en;
hasScannedForEncodings = 0;
knownRegions = (
@@ -1025,9 +1025,10 @@
"$(inherited)",
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include,
);
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
IPHONEOS_DEPLOYMENT_TARGET = 12.4;
MTL_ENABLE_DEBUG_INFO = YES;
ONLY_ACTIVE_ARCH = YES;
REACT_NATIVE_PATH = "${PODS_ROOT}/../../node_modules/react-native";
SDKROOT = iphoneos;
TARGETED_DEVICE_FAMILY = "1,2";
};
@@ -1078,8 +1079,9 @@
"$(inherited)",
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include,
);
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
IPHONEOS_DEPLOYMENT_TARGET = 12.4;
MTL_ENABLE_DEBUG_INFO = NO;
REACT_NATIVE_PATH = "${PODS_ROOT}/../../node_modules/react-native";
SDKROOT = iphoneos;
SWIFT_COMPILATION_MODE = wholemodule;
TARGETED_DEVICE_FAMILY = "1,2";

View File

@@ -98,7 +98,6 @@ 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

@@ -472,7 +472,7 @@
};
};
buildConfigurationList = 0BD906DF1EC0C00300C8C18E /* Build configuration list for PBXProject "sdk" */;
compatibilityVersion = "Xcode 3.2";
compatibilityVersion = "Xcode 12.0";
developmentRegion = en;
hasScannedForEncodings = 0;
knownRegions = (
@@ -526,7 +526,7 @@
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "export NODE_BINARY=node\nexport NODE_ARGS=\"--max_old_space_size=4096\"\n../../node_modules/react-native/scripts/react-native-xcode.sh\n";
shellScript = "WITH_ENVIRONMENT=\"../../node_modules/react-native/scripts/xcode/with-environment.sh\"\nREACT_NATIVE_XCODE=\"../../node_modules/react-native/scripts/react-native-xcode.sh\"\n\n/bin/sh -c \"$WITH_ENVIRONMENT $REACT_NATIVE_XCODE\"\n";
};
26796D8589142D80C8AFDA51 /* [CP] Check Pods Manifest.lock */ = {
isa = PBXShellScriptBuildPhase;
@@ -551,17 +551,12 @@
buildActionMask = 2147483647;
files = (
);
inputPaths = (
"${PODS_ROOT}/Target Support Files/Pods-JitsiMeetSDK/Pods-JitsiMeetSDK-resources.sh",
"${PODS_ROOT}/Amplitude/Sources/Resources/ComodoRsaDomainValidationCA.der",
"${PODS_CONFIGURATION_BUILD_DIR}/GoogleSignIn/GoogleSignIn.bundle",
"${PODS_CONFIGURATION_BUILD_DIR}/React-Core/AccessibilityResources.bundle",
inputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-JitsiMeetSDK/Pods-JitsiMeetSDK-resources-${CONFIGURATION}-input-files.xcfilelist",
);
name = "[CP] Copy Pods Resources";
outputPaths = (
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/ComodoRsaDomainValidationCA.der",
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/GoogleSignIn.bundle",
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/AccessibilityResources.bundle",
outputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-JitsiMeetSDK/Pods-JitsiMeetSDK-resources-${CONFIGURATION}-output-files.xcfilelist",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
@@ -641,15 +636,12 @@
buildActionMask = 2147483647;
files = (
);
inputPaths = (
"${PODS_ROOT}/Target Support Files/Pods-JitsiMeetSDKLite/Pods-JitsiMeetSDKLite-resources.sh",
"${PODS_ROOT}/Amplitude/Sources/Resources/ComodoRsaDomainValidationCA.der",
"${PODS_CONFIGURATION_BUILD_DIR}/React-Core/AccessibilityResources.bundle",
inputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-JitsiMeetSDKLite/Pods-JitsiMeetSDKLite-resources-${CONFIGURATION}-input-files.xcfilelist",
);
name = "[CP] Copy Pods Resources";
outputPaths = (
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/ComodoRsaDomainValidationCA.der",
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/AccessibilityResources.bundle",
outputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-JitsiMeetSDKLite/Pods-JitsiMeetSDKLite-resources-${CONFIGURATION}-output-files.xcfilelist",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
@@ -781,9 +773,10 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
IPHONEOS_DEPLOYMENT_TARGET = 12.4;
MTL_ENABLE_DEBUG_INFO = YES;
ONLY_ACTIVE_ARCH = YES;
REACT_NATIVE_PATH = "${PODS_ROOT}/../../node_modules/react-native";
SDKROOT = iphoneos;
TARGETED_DEVICE_FAMILY = "1,2";
VERSIONING_SYSTEM = "apple-generic";
@@ -837,8 +830,9 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
IPHONEOS_DEPLOYMENT_TARGET = 12.4;
MTL_ENABLE_DEBUG_INFO = NO;
REACT_NATIVE_PATH = "${PODS_ROOT}/../../node_modules/react-native";
SDKROOT = iphoneos;
SWIFT_COMPILATION_MODE = wholemodule;
TARGETED_DEVICE_FAMILY = "1,2";

View File

@@ -156,6 +156,7 @@
"localport_plural": "Portas locais:",
"maxEnabledResolution": "enviar máx",
"more": "Mostrar mais",
"no": "não",
"packetloss": "Perda de pacote:",
"participant_id": "Participante id:",
"quality": {
@@ -174,7 +175,8 @@
"status": "Ligação:",
"transport": "Transporte:",
"transport_plural": "Transportes:",
"video_ssrc": "Vídeo SSRC:"
"video_ssrc": "Vídeo SSRC:",
"yes": "sim"
},
"dateUtils": {
"earlier": "Mais cedo",
@@ -319,13 +321,13 @@
"micPermissionDeniedError": "Não concedeu autorização para utilizar o seu microfone. Ainda pode participar na conferência, mas outros não o ouvirão. Use o botão da câmara na barra de endereço para corrigir isto.",
"micTimeoutError": "Não foi possível iniciar a fonte de áudio. Tempo limite expirado!",
"micUnknownError": "Não pode usar microfone por uma razão desconhecida.",
"moderationAudioLabel": "Permitir aos participantes reativar o som",
"moderationVideoLabel": "Permitir aos participantes reativar o vídeo",
"muteEveryoneDialog": "Os participantes podem reativar o som a qualquer momento.",
"moderationAudioLabel": "Permitir aos participantes ligar o som",
"moderationVideoLabel": "Permitir aos participantes ligar a câmara",
"muteEveryoneDialog": "Os participantes podem ligar o som a qualquer momento.",
"muteEveryoneDialogModerationOn": "Os participantes podem enviar um pedido para falar a qualquer momento.",
"muteEveryoneElseDialog": "Uma vez silenciados, não poderá reativá-los, mas eles podem reativar-se a qualquer momento.",
"muteEveryoneElseDialog": "Uma vez silenciados, não poderá reativá-los, mas eles podem ligar o microfone a qualquer momento.",
"muteEveryoneElseTitle": "Silenciar todos excepto {{whom}}?",
"muteEveryoneElsesVideoDialog": "Quando a câmara for desativada, não poderá voltar a ligá-la, mas eles podem voltar a ligá-la em qualquer momento.",
"muteEveryoneElsesVideoDialog": "Quando a câmara for desligada, não poderá voltar a ligá-la, mas eles podem voltar a ligá-la em qualquer momento.",
"muteEveryoneElsesVideoTitle": "Parar o vídeo de todos excepto {{whom}}?",
"muteEveryoneSelf": "você mesmo",
"muteEveryoneStartMuted": "A partir de agora, toda a gente começa a ficar calada",
@@ -673,8 +675,10 @@
"connectedTwoMembers": "{{first}} e {{second}} entraram na reunião",
"dataChannelClosed": "Deficiência na qualidade do vídeo",
"dataChannelClosedDescription": "O canal de ponte foi desconectado e, portanto, a qualidade do vídeo está limitada à sua configuração mais baixa.",
"disabledIframe": "A incorporação destina-se apenas a fins de demonstração, pelo que esta chamada será desligada em {{timeout}} minutos.",
"disconnected": "desconectado",
"displayNotifications": "Mostrar notificações para",
"dontRemindMe": "Não me lembre",
"focus": "Foco da conferência",
"focusFail": "{{component}} não disponĩvel - tente em {{ms}} seg.",
"gifsMenu": "GIPHY",
@@ -754,8 +758,8 @@
"actions": {
"allow": "Permitir aos participantes:",
"allowVideo": "Permitir vídeo",
"askUnmute": "Pedir para ligar o microfone",
"audioModeration": "Ativar o microfone deles",
"askUnmute": "Pedir para ligar o som",
"audioModeration": "Ligar o microfone deles",
"blockEveryoneMicCamera": "Bloquear o microfone e a câmara de todos",
"invite": "Convidar alguém",
"moreModerationActions": "Mais opções de moderação",
@@ -866,9 +870,11 @@
"lookGood": "O seu microfone funciona corretamente",
"or": "ou",
"premeeting": "Pré-reunião",
"proceedAnyway": "Continuar na mesma",
"screenSharingError": "Erro de partilha de ecrã:",
"showScreen": "Ativar o ecrã de pré-reunião",
"startWithPhone": "Iniciar com o áudio do telefone",
"unsafeRoomConsent": "Compreendo os riscos, quero participar na reunião",
"videoOnlyError": "Erro de vídeo:",
"videoTrackError": "Não foi possível criar a pista de vídeo.",
"viewAllNumbers": "ver todos os números"
@@ -944,7 +950,7 @@
"noStreams": "Não foi detetado nenhum sinal áudio ou vídeo.",
"off": "Gravação parada",
"offBy": "{{name}} parou a gravação",
"on": "Gravando",
"on": "Começou a gravação",
"onBy": "{{name}} iniciou a gravação",
"onlyRecordSelf": "Gravar apenas as minhas transmissões áudio e vídeo",
"pending": "Preparando para gravar a reunião...",
@@ -970,8 +976,14 @@
"security": {
"about": "Pode adicionar uma $t(lockRoomPassword) à sua reunião. Os participantes terão de fornecer a $t(lockRoomPassword) antes de serem autorizados a participar na reunião.",
"aboutReadOnly": "Os participantes moderadores podem acrescentar uma $t(lockRoomPassword) à reunião. Os participantes terão de fornecer a $t(lockRoomPassword) antes de serem autorizados a participar na reunião.",
"insecureRoomNameWarning": "O nome da sala é inseguro. Participantes indesejados podem juntar-se à sua conferência. Considere proteger a sua reunião utilizando o botão de segurança.",
"title": "Opções de segurança"
"insecureRoomNameWarningNative": "O nome da sala não é seguro. Participantes indesejados podem juntar-se à sua reunião. {{recommendAction}} Saiba mais sobre como proteger a sua reunião ",
"insecureRoomNameWarningWeb": "O nome da sala não é seguro. Participantes indesejados podem juntar-se à sua reunião. {{recommendAction}} Saiba mais sobre como proteger a sua reunião <a href=\"{{securityUrl}}\" rel=\"security\" target=\"_blank\">here</a>.",
"title": "Opções de Segurança",
"unsafeRoomActions": {
"meeting": "Considere a possibilidade de proteger a sua reunião utilizando o botão de segurança.",
"prejoin": "Considere a utilização de um nome de reunião mais personalizado.",
"welcome": "Considere utilizar um nome de reunião mais personalizado ou selecione uma das sugestões."
}
},
"settings": {
"audio": "Áudio",
@@ -1146,6 +1158,7 @@
"privateMessage": "Enviar mensagem privada",
"profile": "Editar o seu perfil",
"raiseHand": "Levantar a mão",
"reactions": "Reações",
"reactionsMenu": "Menu de reações",
"recording": "Mudar gravação",
"remoteMute": "Participante sem som",
@@ -1243,6 +1256,7 @@
"reactionLike": "Enviar reação de aprovado",
"reactionSilence": "Enviar reação de silêncio",
"reactionSurprised": "Enviar reação de surpreendido",
"reactions": "Reações",
"security": "Opções de segurança",
"selectBackground": "Selecionar plano de fundo",
"shareRoom": "Convidar alguém",
@@ -1376,7 +1390,14 @@
"webAssemblyWarning": "WebAssembly não suportado",
"webAssemblyWarningDescription": "WebAssembly desactivado ou não suportado por este navegador"
},
"visitorsLabel": "Número de visitantes: {{count}}",
"visitors": {
"chatIndicator": "(visitante)",
"labelTooltip": "Número de visitantes: {{count}}",
"notification": {
"description": "Para participar levante a sua mão",
"title": "É um visitante na reunião"
}
},
"volumeSlider": "Controlo de volume",
"welcomepage": {
"accessibilityLabel": {

View File

@@ -870,9 +870,11 @@
"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"
@@ -974,8 +976,14 @@
"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.",
"insecureRoomNameWarning": "The room name is unsafe. Unwanted participants may join your conference. Consider securing your meeting using the security button.",
"title": "Security Options"
"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."
}
},
"settings": {
"audio": "Audio",
@@ -1150,6 +1158,7 @@
"privateMessage": "Send private message",
"profile": "Edit your profile",
"raiseHand": "Raise your hand",
"reactions": "Reactions",
"reactionsMenu": "Reactions menu",
"recording": "Toggle recording",
"remoteMute": "Mute participant",
@@ -1247,6 +1256,7 @@
"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

@@ -1,5 +1,4 @@
// @flow
/* global APP */
import Logger from '@jitsi/logger';
import { createApiEvent } from '../../react/features/analytics/AnalyticsEvents';
@@ -113,6 +112,7 @@ 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 { getJitsiMeetTransport } from '../transport';
import {
@@ -123,8 +123,6 @@ import {
const logger = Logger.getLogger(__filename);
declare var APP: Object;
/**
* List of the available commands.
*/
@@ -833,6 +831,9 @@ function initCommands() {
} else {
logger.error(' End Conference not supported');
}
},
'toggle-whiteboard': () => {
APP.store.dispatch(toggleWhiteboard());
}
};
transport.on('event', ({ data, name }) => {
@@ -1030,7 +1031,7 @@ function toggleScreenSharing(enable) {
* @param {MouseEvent} event - The mouse event to sanitize.
* @returns {Object}
*/
function sanitizeMouseEvent(event: MouseEvent) {
function sanitizeMouseEvent(event) {
const {
clientX,
clientY,
@@ -1068,7 +1069,7 @@ function sanitizeMouseEvent(event: MouseEvent) {
* Jitsi Meet.
*/
class API {
_enabled: boolean;
_enabled;
/**
* Initializes the API. Setups message event listeners that will receive
@@ -1103,7 +1104,7 @@ class API {
* otherwise.
* @returns {void}
*/
notifyLargeVideoVisibilityChanged(isHidden: boolean) {
notifyLargeVideoVisibilityChanged(isHidden) {
this._sendEvent({
name: 'large-video-visibility-changed',
isVisible: !isHidden
@@ -1117,7 +1118,7 @@ class API {
* @param {Object} event - The message to pass onto spot.
* @returns {void}
*/
sendProxyConnectionEvent(event: Object) {
sendProxyConnectionEvent(event) {
this._sendEvent({
name: 'proxy-connection-event',
...event
@@ -1130,7 +1131,7 @@ class API {
* @param {Object} event - The event to be sent.
* @returns {void}
*/
_sendEvent(event: Object = {}) {
_sendEvent(event = {}) {
if (this._enabled) {
transport.sendEvent(event);
}
@@ -1143,7 +1144,7 @@ class API {
* @param {boolean} isOpen - True if the chat panel is open.
* @returns {void}
*/
notifyChatUpdated(unreadCount: number, isOpen: boolean) {
notifyChatUpdated(unreadCount, isOpen) {
this._sendEvent({
name: 'chat-updated',
unreadCount,
@@ -1158,7 +1159,7 @@ class API {
* @param {boolean} privateMessage - True if the message was a private message.
* @returns {void}
*/
notifySendingChatMessage(message: string, privateMessage: boolean) {
notifySendingChatMessage(message, privateMessage) {
this._sendEvent({
name: 'outgoing-message',
message,
@@ -1172,7 +1173,7 @@ class API {
* @param {MouseEvent} event - The mousemove event.
* @returns {void}
*/
notifyMouseEnter(event: MouseEvent) {
notifyMouseEnter(event) {
this._sendEvent({
name: 'mouse-enter',
event: sanitizeMouseEvent(event)
@@ -1185,7 +1186,7 @@ class API {
* @param {MouseEvent} event - The mousemove event.
* @returns {void}
*/
notifyMouseLeave(event: MouseEvent) {
notifyMouseLeave(event) {
this._sendEvent({
name: 'mouse-leave',
event: sanitizeMouseEvent(event)
@@ -1198,7 +1199,7 @@ class API {
* @param {MouseEvent} event - The mousemove event.
* @returns {void}
*/
notifyMouseMove(event: MouseEvent) {
notifyMouseMove(event) {
this._sendEvent({
name: 'mouse-move',
event: sanitizeMouseEvent(event)
@@ -1212,7 +1213,7 @@ class API {
* @param {boolean} enabled - Whether or not the new moderation status is enabled.
* @returns {void}
*/
notifyModerationChanged(mediaType: string, enabled: boolean) {
notifyModerationChanged(mediaType, enabled) {
this._sendEvent({
name: 'moderation-status-changed',
mediaType,
@@ -1227,7 +1228,7 @@ class API {
* @param {string} mediaType - Media type for which the participant was approved.
* @returns {void}
*/
notifyParticipantApproved(participantId: string, mediaType: string) {
notifyParticipantApproved(participantId, mediaType) {
this._sendEvent({
name: 'moderation-participant-approved',
id: participantId,
@@ -1242,7 +1243,7 @@ class API {
* @param {string} mediaType - Media type for which the participant was rejected.
* @returns {void}
*/
notifyParticipantRejected(participantId: string, mediaType: string) {
notifyParticipantRejected(participantId, mediaType) {
this._sendEvent({
name: 'moderation-participant-rejected',
id: participantId,
@@ -1258,7 +1259,7 @@ class API {
*
* @returns {void}
*/
notifyNotificationTriggered(title: string, description: string) {
notifyNotificationTriggered(title, description) {
this._sendEvent({
description,
name: 'notification-triggered',
@@ -1272,7 +1273,7 @@ class API {
* @param {number} videoQuality - The video quality. The number represents the maximum height of the video streams.
* @returns {void}
*/
notifyVideoQualityChanged(videoQuality: number) {
notifyVideoQualityChanged(videoQuality) {
this._sendEvent({
name: 'video-quality-changed',
videoQuality
@@ -1287,9 +1288,7 @@ class API {
* @returns {void}
*/
notifyReceivedChatMessage(
{ body, id, nick, privateMessage, ts }: {
body: *, id: string, nick: string, privateMessage: boolean, ts: *
} = {}) {
{ body, id, nick, privateMessage, ts } = {}) {
if (APP.conference.isLocalId(id)) {
return;
}
@@ -1312,7 +1311,7 @@ class API {
* @param {Object} props - The display name of the user.
* @returns {void}
*/
notifyUserJoined(id: string, props: Object) {
notifyUserJoined(id, props) {
this._sendEvent({
name: 'participant-joined',
id,
@@ -1327,7 +1326,7 @@ class API {
* @param {string} id - User id.
* @returns {void}
*/
notifyUserLeft(id: string) {
notifyUserLeft(id) {
this._sendEvent({
name: 'participant-left',
id
@@ -1342,7 +1341,7 @@ class API {
* @param {string} role - The new user role.
* @returns {void}
*/
notifyUserRoleChanged(id: string, role: string) {
notifyUserRoleChanged(id, role) {
this._sendEvent({
name: 'participant-role-changed',
id,
@@ -1358,7 +1357,7 @@ class API {
* @param {string} avatarURL - The new avatar URL of the participant.
* @returns {void}
*/
notifyAvatarChanged(id: string, avatarURL: string) {
notifyAvatarChanged(id, avatarURL) {
this._sendEvent({
name: 'avatar-changed',
avatarURL,
@@ -1373,7 +1372,7 @@ class API {
* @param {Object} data - The event data.
* @returns {void}
*/
notifyEndpointTextMessageReceived(data: Object) {
notifyEndpointTextMessageReceived(data) {
this._sendEvent({
name: 'endpoint-text-message-received',
data
@@ -1387,7 +1386,7 @@ class API {
* @param {string} faceExpression - Detected face expression.
* @returns {void}
*/
notifyFaceLandmarkDetected(faceBox: Object, faceExpression: string) {
notifyFaceLandmarkDetected(faceBox, faceExpression) {
this._sendEvent({
name: 'face-landmark-detected',
faceBox,
@@ -1401,7 +1400,7 @@ class API {
* @param {Object} data - The event data.
* @returns {void}
*/
notifySharingParticipantsChanged(data: Object) {
notifySharingParticipantsChanged(data) {
this._sendEvent({
name: 'content-sharing-participants-changed',
data
@@ -1415,7 +1414,7 @@ class API {
* @param {Object} devices - The new device list.
* @returns {void}
*/
notifyDeviceListChanged(devices: Object) {
notifyDeviceListChanged(devices) {
this._sendEvent({
name: 'device-list-changed',
devices
@@ -1433,8 +1432,8 @@ class API {
* @returns {void}
*/
notifyDisplayNameChanged(
id: string,
{ displayName, formattedDisplayName }: Object) {
id,
{ displayName, formattedDisplayName }) {
this._sendEvent({
name: 'display-name-change',
displayname: displayName,
@@ -1452,8 +1451,8 @@ class API {
* @returns {void}
*/
notifyEmailChanged(
id: string,
{ email }: Object) {
id,
{ email }) {
this._sendEvent({
name: 'email-change',
email,
@@ -1465,10 +1464,10 @@ class API {
* Notify external application (if API is enabled) that the an error has been logged.
*
* @param {string} logLevel - The message log level.
* @param {Array} args - Array of strings composing the log message.
* @param {Array<string>} args - Array of strings composing the log message.
* @returns {void}
*/
notifyLog(logLevel: string, args: Array<string>) {
notifyLog(logLevel, args) {
this._sendEvent({
name: 'log',
logLevel,
@@ -1486,7 +1485,7 @@ class API {
* user and the type of the room.
* @returns {void}
*/
notifyConferenceJoined(roomName: string, id: string, props: Object) {
notifyConferenceJoined(roomName, id, props) {
this._sendEvent({
name: 'video-conference-joined',
roomName,
@@ -1501,7 +1500,7 @@ class API {
* @param {string} roomName - User id.
* @returns {void}
*/
notifyConferenceLeft(roomName: string) {
notifyConferenceLeft(roomName) {
this._sendEvent({
name: 'video-conference-left',
roomName
@@ -1516,7 +1515,7 @@ class API {
*
* @returns {void}
*/
notifyDataChannelClosed(code: number, reason: string) {
notifyDataChannelClosed(code, reason) {
this._sendEvent({
name: 'data-channel-closed',
code,
@@ -1559,7 +1558,7 @@ class API {
* @param {boolean} muted - The new muted status.
* @returns {void}
*/
notifyAudioMutedStatusChanged(muted: boolean) {
notifyAudioMutedStatusChanged(muted) {
this._sendEvent({
name: 'audio-mute-status-changed',
muted
@@ -1573,7 +1572,7 @@ class API {
* @param {boolean} muted - The new muted status.
* @returns {void}
*/
notifyVideoMutedStatusChanged(muted: boolean) {
notifyVideoMutedStatusChanged(muted) {
this._sendEvent({
name: 'video-mute-status-changed',
muted
@@ -1587,7 +1586,7 @@ class API {
* @param {boolean} available - True if available and false otherwise.
* @returns {void}
*/
notifyAudioAvailabilityChanged(available: boolean) {
notifyAudioAvailabilityChanged(available) {
audioAvailable = available;
this._sendEvent({
name: 'audio-availability-changed',
@@ -1602,7 +1601,7 @@ class API {
* @param {boolean} available - True if available and false otherwise.
* @returns {void}
*/
notifyVideoAvailabilityChanged(available: boolean) {
notifyVideoAvailabilityChanged(available) {
videoAvailable = available;
this._sendEvent({
name: 'video-availability-changed',
@@ -1617,7 +1616,7 @@ class API {
* @param {string} id - User id of the new on stage participant.
* @returns {void}
*/
notifyOnStageParticipantChanged(id: string) {
notifyOnStageParticipantChanged(id) {
this._sendEvent({
name: 'on-stage-participant-changed',
id
@@ -1631,7 +1630,7 @@ class API {
* @param {boolean} isVisible - Whether the prejoin video is visible.
* @returns {void}
*/
notifyPrejoinVideoVisibilityChanged(isVisible: boolean) {
notifyPrejoinVideoVisibilityChanged(isVisible) {
this._sendEvent({
name: 'on-prejoin-video-changed',
isVisible
@@ -1665,7 +1664,7 @@ class API {
* @param {string} message - Additional information about the error.
* @returns {void}
*/
notifyOnCameraError(type: string, message: string) {
notifyOnCameraError(type, message) {
this._sendEvent({
name: 'camera-error',
type,
@@ -1681,7 +1680,7 @@ class API {
* @param {string} message - Additional information about the error.
* @returns {void}
*/
notifyOnMicError(type: string, message: string) {
notifyOnMicError(type, message) {
this._sendEvent({
name: 'mic-error',
type,
@@ -1697,7 +1696,7 @@ class API {
* @param {string} error - A failure message, if any.
* @returns {void}
*/
notifyFeedbackSubmitted(error: string) {
notifyFeedbackSubmitted(error) {
this._sendEvent({
name: 'feedback-submitted',
error
@@ -1722,7 +1721,7 @@ class API {
* be displayed or hidden.
* @returns {void}
*/
notifyFilmstripDisplayChanged(visible: boolean) {
notifyFilmstripDisplayChanged(visible) {
this._sendEvent({
name: 'filmstrip-display-changed',
visible
@@ -1739,7 +1738,7 @@ class API {
* other participant.
* @returns {void}
*/
notifyKickedOut(kicked: Object, kicker: Object) {
notifyKickedOut(kicked, kicker) {
this._sendEvent({
name: 'participant-kicked-out',
kicked,
@@ -1768,7 +1767,7 @@ class API {
* share is capturing.
* @returns {void}
*/
notifyScreenSharingStatusChanged(on: boolean, details: Object) {
notifyScreenSharingStatusChanged(on, details) {
this._sendEvent({
name: 'screen-sharing-status-changed',
on,
@@ -1783,7 +1782,7 @@ class API {
* @param {string} id - Id of the dominant participant.
* @returns {void}
*/
notifyDominantSpeakerChanged(id: string) {
notifyDominantSpeakerChanged(id) {
this._sendEvent({
name: 'dominant-speaker-changed',
id
@@ -1797,7 +1796,7 @@ class API {
* @param {string} subject - Conference subject.
* @returns {void}
*/
notifySubjectChanged(subject: string) {
notifySubjectChanged(subject) {
this._sendEvent({
name: 'subject-change',
subject
@@ -1812,7 +1811,7 @@ class API {
* otherwise.
* @returns {void}
*/
notifyTileViewChanged(enabled: boolean) {
notifyTileViewChanged(enabled) {
this._sendEvent({
name: 'tile-view-changed',
enabled
@@ -1825,7 +1824,7 @@ class API {
* @param {string} localStorageContent - The new localStorageContent.
* @returns {void}
*/
notifyLocalStorageChanged(localStorageContent: string) {
notifyLocalStorageChanged(localStorageContent) {
this._sendEvent({
name: 'local-storage-changed',
localStorageContent
@@ -1839,7 +1838,7 @@ class API {
* @param {boolean} handRaised - Whether user has raised hand.
* @returns {void}
*/
notifyRaiseHandUpdated(id: string, handRaised: boolean) {
notifyRaiseHandUpdated(id, handRaised) {
this._sendEvent({
name: 'raise-hand-updated',
handRaised,
@@ -1855,7 +1854,7 @@ class API {
* @param {string} error - Error type or null if success.
* @returns {void}
*/
notifyRecordingStatusChanged(on: boolean, mode: string, error?: string) {
notifyRecordingStatusChanged(on, mode, error) {
this._sendEvent({
name: 'recording-status-changed',
on,
@@ -1872,7 +1871,7 @@ class API {
* @param {number} ttl - The recording download link time to live.
* @returns {void}
*/
notifyRecordingLinkAvailable(link: string, ttl: number) {
notifyRecordingLinkAvailable(link, ttl) {
this._sendEvent({
name: 'recording-link-available',
link,
@@ -1886,7 +1885,7 @@ class API {
* @param {Object} participant - Participant data such as id and name.
* @returns {void}
*/
notifyKnockingParticipant(participant: Object) {
notifyKnockingParticipant(participant) {
this._sendEvent({
name: 'knocking-participant',
participant
@@ -1899,7 +1898,7 @@ class API {
* @param {Object} error - The error.
* @returns {void}
*/
notifyError(error: Object) {
notifyError(error) {
this._sendEvent({
name: 'error-occurred',
error
@@ -1913,7 +1912,7 @@ class API {
* @param {boolean} preventExecution - Whether execution of the button click was prevented or not.
* @returns {void}
*/
notifyToolbarButtonClicked(key: string, preventExecution: boolean) {
notifyToolbarButtonClicked(key, preventExecution) {
this._sendEvent({
name: 'toolbar-button-clicked',
key,
@@ -1927,7 +1926,7 @@ class API {
* @param {boolean} supported - If browser is supported or not.
* @returns {void}
*/
notifyBrowserSupport(supported: boolean) {
notifyBrowserSupport(supported) {
this._sendEvent({
name: 'browser-support',
supported
@@ -2014,6 +2013,20 @@ 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) {
this._sendEvent({
name: 'whiteboard-status-changed',
status
});
}
/**
* Disposes the allocated resources.
*

View File

@@ -36,7 +36,6 @@ const commands = {
cancelPrivateChat: 'cancel-private-chat',
closeBreakoutRoom: 'close-breakout-room',
displayName: 'display-name',
e2eeKey: 'e2ee-key',
endConference: 'end-conference',
email: 'email',
grantModerator: 'grant-moderator',
@@ -90,7 +89,8 @@ const commands = {
toggleSubtitles: 'toggle-subtitles',
toggleTileView: 'toggle-tile-view',
toggleVirtualBackgroundDialog: 'toggle-virtual-background',
toggleVideo: 'toggle-video'
toggleVideo: 'toggle-video',
toggleWhiteboard: 'toggle-whiteboard'
};
/**
@@ -154,7 +154,8 @@ const events = {
'subject-change': 'subjectChange',
'suspend-detected': 'suspendDetected',
'tile-view-changed': 'tileViewChanged',
'toolbar-button-clicked': 'toolbarButtonClicked'
'toolbar-button-clicked': 'toolbarButtonClicked',
'whiteboard-status-changed': 'whiteboardStatusChanged'
};
/**
@@ -314,6 +315,7 @@ 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();
@@ -331,7 +333,8 @@ export default class JitsiMeetExternalAPI extends EventEmitter {
devices,
userInfo,
e2eeKey,
release
release,
sandbox = ''
} = parseArguments(args);
const localStorageContent = jitsiLocalStorage.getItem('jitsiLocalStorage');
@@ -349,7 +352,7 @@ export default class JitsiMeetExternalAPI extends EventEmitter {
},
release
});
this._createIFrame(height, width, onload);
this._createIFrame(height, width, onload, sandbox);
this._transport = new Transport({
backend: new PostMessageTransportBackend({
postisOptions: {
@@ -382,11 +385,12 @@ 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) {
_createIFrame(height, width, onload, sandbox) {
const frameName = `jitsiConferenceFrame${id}`;
this._frame = document.createElement('iframe');
@@ -397,6 +401,10 @@ 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
@@ -552,7 +560,22 @@ export default class JitsiMeetExternalAPI extends EventEmitter {
switch (name) {
case 'video-conference-joined': {
if (typeof this._tmpE2EEKey !== 'undefined') {
this.executeCommand(commands.e2eeKey, this._tmpE2EEKey);
const hexToBytes = hex => {
const bytes = [];
for (let c = 0; c < hex.length; c += 2) {
bytes.push(parseInt(hex.substring(c, c + 2), 16));
}
return bytes;
};
this.executeCommand('setMediaEncryptionKey', JSON.stringify({
exportedKey: hexToBytes(this._tmpE2EEKey),
index: 0
}));
this._tmpE2EEKey = undefined;
}

View File

@@ -1,5 +1,3 @@
// @flow
import Logger from '@jitsi/logger';
const logger = Logger.getLogger(__filename);
@@ -11,7 +9,7 @@ const logger = Logger.getLogger(__filename);
* the external communication.
* @returns {Promise}
*/
export function getAvailableDevices(transport: Object) {
export function getAvailableDevices(transport) {
return transport.sendRequest({
type: 'devices',
name: 'getAvailableDevices'
@@ -29,7 +27,7 @@ export function getAvailableDevices(transport: Object) {
* the external communication.
* @returns {Promise}
*/
export function getCurrentDevices(transport: Object) {
export function getCurrentDevices(transport) {
return transport.sendRequest({
type: 'devices',
name: 'getCurrentDevices'
@@ -50,7 +48,7 @@ export function getCurrentDevices(transport: Object) {
* Default - 'input'.
* @returns {Promise}
*/
export function isDeviceChangeAvailable(transport: Object, deviceType: string) {
export function isDeviceChangeAvailable(transport, deviceType) {
return transport.sendRequest({
deviceType,
type: 'devices',
@@ -66,7 +64,7 @@ export function isDeviceChangeAvailable(transport: Object, deviceType: string) {
* the external communication.
* @returns {Promise}
*/
export function isDeviceListAvailable(transport: Object) {
export function isDeviceListAvailable(transport) {
return transport.sendRequest({
type: 'devices',
name: 'isDeviceListAvailable'
@@ -81,7 +79,7 @@ export function isDeviceListAvailable(transport: Object) {
* the external communication.
* @returns {Promise}
*/
export function isMultipleAudioInputSupported(transport: Object) {
export function isMultipleAudioInputSupported(transport) {
return transport.sendRequest({
type: 'devices',
name: 'isMultipleAudioInputSupported'
@@ -97,7 +95,7 @@ export function isMultipleAudioInputSupported(transport: Object) {
* @param {string} id - The id of the new device.
* @returns {Promise}
*/
export function setAudioInputDevice(transport: Object, label: string, id: string) {
export function setAudioInputDevice(transport, label, id) {
return _setDevice(transport, {
id,
kind: 'audioinput',
@@ -114,7 +112,7 @@ export function setAudioInputDevice(transport: Object, label: string, id: string
* @param {string} id - The id of the new device.
* @returns {Promise}
*/
export function setAudioOutputDevice(transport: Object, label: string, id: string) {
export function setAudioOutputDevice(transport, label, id) {
return _setDevice(transport, {
id,
kind: 'audiooutput',
@@ -130,7 +128,7 @@ export function setAudioOutputDevice(transport: Object, label: string, id: strin
* @param {Object} device - The new device to be used.
* @returns {Promise}
*/
function _setDevice(transport: Object, device) {
function _setDevice(transport, device) {
return transport.sendRequest({
type: 'devices',
name: 'setDevice',
@@ -147,7 +145,7 @@ function _setDevice(transport: Object, device) {
* @param {string} id - The id of the new device.
* @returns {Promise}
*/
export function setVideoInputDevice(transport: Object, label: string, id: string) {
export function setVideoInputDevice(transport, label, id) {
return _setDevice(transport, {
id,
kind: 'videoinput',

View File

@@ -1,5 +1,4 @@
// @flow
/* global APP */
import Logger from '@jitsi/logger';
import { openConnection } from '../../../connection';
@@ -23,7 +22,6 @@ import ExternalLoginDialog from './LoginDialog';
let externalAuthWindow;
declare var APP: Object;
const logger = Logger.getLogger(__filename);
@@ -77,7 +75,7 @@ function doExternalAuth(room, lockPassword) {
* back with "?jwt={the JWT token}" query parameter added.
* @param {string} [roomName] the name of the conference room.
*/
export function redirectToTokenAuthService(roomName: string) {
export function redirectToTokenAuthService(roomName) {
const config = APP.store.getState()['features/base/config'];
// FIXME: This method will not preserve the other URL params that were
@@ -174,7 +172,7 @@ function initJWTTokenListener(room) {
* @param {JitsiConference} room
* @param {string} [lockPassword] password to use if the conference is locked
*/
function authenticate(room: Object, lockPassword: string) {
function authenticate(room, lockPassword) {
const config = APP.store.getState()['features/base/config'];
if (isTokenAuthEnabled(config) || room.isExternalAuthEnabled()) {
@@ -189,7 +187,7 @@ function authenticate(room: Object, lockPassword: string) {
* @param {JitsiConference} room
* @param {string} [lockPassword] password to use if the conference is locked
*/
function requireAuth(room: Object, lockPassword: string) {
function requireAuth(room, lockPassword) {
if (isDialogOpen(APP.store, WaitForOwnerDialog) || isDialogOpen(APP.store, LoginDialog)) {
return;
}
@@ -207,7 +205,7 @@ function requireAuth(room: Object, lockPassword: string) {
* @param {string} [lockPassword] password to use if the conference is locked
* @returns {Promise}
*/
function logout(room: Object) {
function logout(room) {
return new Promise(resolve => {
room.room.moderator.logout(resolve);
}).then(url => {

View File

@@ -1,6 +1,4 @@
// @flow
declare var APP: Object;
/* global APP */
export default {
@@ -10,7 +8,7 @@ export default {
* @param {function} callback - callback to invoke when auth popup is closed.
* @returns auth dialog
*/
showExternalAuthDialog(url: string, callback: ?Function) {
showExternalAuthDialog(url, callback) {
const dialog = APP.UI.messageHandler.openCenteredPopup(
url, 910, 660,

View File

@@ -1,15 +1,8 @@
/* @flow */
import $ from 'jquery';
import jqueryI18next from 'jquery-i18next';
import i18next from '../../react/features/base/i18n/i18next';
type DocumentElement = {
lang: string
}
/**
* Notifies that the {@link i18next} instance has finished its initialization.
*
@@ -18,7 +11,7 @@ type DocumentElement = {
*/
function _onI18nInitialized() {
const documentElement: DocumentElement
const documentElement
= document.documentElement || {};
$('[data-i18n]').localize();
@@ -47,7 +40,7 @@ class Translation {
/**
*
*/
generateTranslationHTML(key: string, options: Object) {
generateTranslationHTML(key, options) {
const optAttr
= options ? ` data-i18n-options='${JSON.stringify(options)}'` : '';
@@ -60,7 +53,7 @@ class Translation {
/**
*
*/
translateElement(selector: Object, options: Object) {
translateElement(selector, options) {
// XXX i18next expects undefined if options are missing.
selector.localize(options ? options : undefined);
}

5412
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -22,7 +22,7 @@
"@giphy/react-components": "6.8.1",
"@giphy/react-native-sdk": "1.7.0",
"@hapi/bourne": "2.0.0",
"@jitsi/excalidraw": "https://github.com/jitsi/excalidraw/releases/download/v0.0.12/jitsi-excalidraw-0.0.12.tgz",
"@jitsi/excalidraw": "https://github.com/jitsi/excalidraw/releases/download/v0.0.13/jitsi-excalidraw-0.0.13.tgz",
"@jitsi/js-utils": "2.0.5",
"@jitsi/logger": "2.0.0",
"@jitsi/rnnoise-wasm": "0.1.0",
@@ -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/v1626.0.0+a41aa571/lib-jitsi-meet.tgz",
"lib-jitsi-meet": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v1635.0.0+152fdb21/lib-jitsi-meet.tgz",
"lodash": "4.17.21",
"moment": "2.29.4",
"moment-duration-format": "2.2.2",
@@ -73,13 +73,13 @@
"optional-require": "1.0.3",
"promise.allsettled": "1.0.4",
"punycode": "2.1.1",
"react": "17.0.2",
"react-dom": "17.0.2",
"react": "18.2.0",
"react-dom": "18.2.0",
"react-emoji-render": "1.2.4",
"react-focus-lock": "2.5.1",
"react-focus-lock": "2.9.4",
"react-i18next": "10.11.4",
"react-linkify": "1.0.0-alpha",
"react-native": "0.68.6",
"react-native": "0.69.10",
"react-native-background-timer": "2.4.1",
"react-native-calendar-events": "2.2.0",
"react-native-callstats": "3.73.7",
@@ -125,10 +125,9 @@
},
"devDependencies": {
"@babel/core": "7.16.0",
"@babel/eslint-parser": "7.16.0",
"@babel/eslint-parser": "7.21.8",
"@babel/plugin-proposal-export-default-from": "7.16.0",
"@babel/preset-env": "7.16.0",
"@babel/preset-flow": "7.16.0",
"@babel/preset-react": "7.16.0",
"@jitsi/eslint-config": "4.1.5",
"@types/amplitude-js": "8.16.2",
@@ -139,7 +138,8 @@
"@types/react": "17.0.14",
"@types/react-dom": "17.0.14",
"@types/react-linkify": "1.0.1",
"@types/react-native": "0.68.9",
"@types/react-native": "0.69.20",
"@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",
@@ -149,29 +149,28 @@
"@types/w3c-image-capture": "1.0.6",
"@types/w3c-web-hid": "1.0.3",
"@types/zxcvbn": "4.4.1",
"@typescript-eslint/eslint-plugin": "5.30.5",
"@typescript-eslint/parser": "5.30.4",
"@typescript-eslint/eslint-plugin": "5.59.5",
"@typescript-eslint/parser": "5.59.5",
"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.35.0",
"eslint-plugin-flowtype": "8.0.3",
"eslint-plugin-import": "2.25.2",
"eslint": "8.40.0",
"eslint-plugin-import": "2.27.5",
"eslint-plugin-jsdoc": "37.0.3",
"eslint-plugin-react": "7.26.1",
"eslint-plugin-react-native": "3.11.0",
"eslint-plugin-typescript-sort-keys": "2.1.0",
"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",
"metro-react-native-babel-preset": "0.67.0",
"metro-react-native-babel-preset": "0.70.3",
"patch-package": "6.4.7",
"process": "0.11.10",
"sass": "1.26.8",
"style-loader": "3.3.1",
"traverse": "0.6.6",
"ts-loader": "9.4.1",
"typescript": "4.7.4",
"ts-loader": "9.4.2",
"typescript": "5.0.4",
"unorm": "1.6.0",
"webpack": "5.76.0",
"webpack-bundle-analyzer": "4.4.2",
@@ -179,7 +178,7 @@
"webpack-dev-server": "4.7.3"
},
"overrides": {
"strophe.js@1.6.0": {
"strophe.js@1.5.0": {
"@xmldom/xmldom": "0.8.7"
}
},

View File

@@ -1,11 +0,0 @@
diff --git a/node_modules/eslint-plugin-flowtype/dist/configs/recommended.json b/node_modules/eslint-plugin-flowtype/dist/configs/recommended.json
index 90a0d69..2ad7d68 100644
--- a/node_modules/eslint-plugin-flowtype/dist/configs/recommended.json
+++ b/node_modules/eslint-plugin-flowtype/dist/configs/recommended.json
@@ -1,5 +1,5 @@
{
- "parser": "@babel/eslint",
+ "parser": "@babel/eslint-parser",
"parserOptions": {
"babelOptions": {
"plugins": [

View File

@@ -26,10 +26,5 @@ module.exports = {
ios: null
}
}
},
project: {
ios: {
project: '.ios/jitsi-meet.xcworkspace'
}
}
};

View File

@@ -1,7 +1,6 @@
module.exports = {
'extends': [
'../.eslintrc.js',
'@jitsi/eslint-config/flow',
'@jitsi/eslint-config/jsdoc',
'@jitsi/eslint-config/react',
'.eslintrc-react-native.js'
@@ -30,15 +29,10 @@ module.exports = {
}
],
'rules': {
'flowtype/no-types-missing-file-annotation': 0,
// XXX remove this eventually.
'react/jsx-indent-props': 0
},
'settings': {
'flowtype': {
'onlyFilesWithFlowAnnotation': true
},
'react': {
'version': 'detect'
}

View File

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

View File

@@ -1,11 +1,9 @@
// @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 type { Props } from '../base/toolbox/components/AbstractButton';
import { IProps } from '../base/toolbox/components/AbstractButton';
import ToolbarButton from './ToolbarButton';
@@ -14,23 +12,25 @@ const { api } = window.alwaysOnTop;
/**
* The type of the React {@code Component} state of {@link AudioMuteButton}.
*/
type State = {
interface IState {
/**
* Whether audio is available is not.
*/
audioAvailable: boolean,
audioAvailable: boolean;
/**
* Whether audio is muted or not.
*/
audioMuted: boolean
};
audioMuted: boolean;
}
type Props = Partial<IProps>;
/**
* Stateless "mute/unmute audio" button for the Always-on-Top windows.
*/
export default class AudioMuteButton extends Component<Props, State> {
export default class AudioMuteButton extends Component<Props, IState> {
icon = IconMic;
toggledIcon = IconMicSlash;
accessibilityLabel = 'Audio mute';
@@ -38,7 +38,7 @@ export default class AudioMuteButton extends Component<Props, State> {
/**
* Initializes a new {@code AudioMuteButton} instance.
*
* @param {Props} props - The React {@code Component} props to initialize
* @param {IProps} props - The React {@code Component} props to initialize
* the new {@code AudioMuteButton} instance with.
*/
constructor(props: Props) {
@@ -94,27 +94,23 @@ export default class AudioMuteButton extends Component<Props, State> {
this._audioMutedListener);
}
_audioAvailabilityListener: ({ available: boolean }) => void;
/**
* Handles audio available api events.
*
* @param {{ available: boolean }} status - The new available status.
* @returns {void}
*/
_audioAvailabilityListener({ available }) {
_audioAvailabilityListener({ available }: { available: boolean; }) {
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 }) {
_audioMutedListener({ muted }: { muted: boolean; }) {
this.setState({ audioMuted: muted });
}
@@ -144,16 +140,14 @@ export default class AudioMuteButton extends Component<Props, State> {
* 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) { // eslint-disable-line no-unused-vars
_setAudioMuted(_audioMuted: boolean) {
this.state.audioAvailable && api.executeCommand('toggleAudio');
}
_onClick: () => {};
/**
* Handles clicking / pressing the button, and toggles the audio mute state
* accordingly.
@@ -173,11 +167,13 @@ export default class AudioMuteButton extends Component<Props, State> {
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,19 +1,20 @@
// @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 type { Props } from '../base/toolbox/components/AbstractButton';
import { IProps } 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;
@@ -21,7 +22,7 @@ export default class HangupButton extends Component<Props, *> {
/**
* Initializes a new {@code HangupButton} instance.
*
* @param {Props} props - The React {@code Component} props to initialize
* @param {IProps} props - The React {@code Component} props to initialize
* the new {@code HangupButton} instance with.
*/
constructor(props: Props) {
@@ -31,8 +32,6 @@ export default class HangupButton extends Component<Props, *> {
this._onClick = this._onClick.bind(this);
}
_onClick: () => {};
/**
* Handles clicking / pressing the button, and disconnects the conference.
*
@@ -50,10 +49,12 @@ 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,5 +1,3 @@
// @flow
import React, { Component } from 'react';
import AudioMuteButton from './AudioMuteButton';
@@ -9,30 +7,30 @@ import VideoMuteButton from './VideoMuteButton';
/**
* The type of the React {@code Component} props of {@link Toolbar}.
*/
type Props = {
interface IProps {
/**
* 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: Function,
onMouseOut: (e?: React.MouseEvent) => void;
/**
* Callback invoked when the mouse has moved over the toolbar.
*/
onMouseOver: Function
};
onMouseOver: (e?: React.MouseEvent) => void;
}
/**
* Represents the toolbar in the Always On Top window.
*
* @augments Component
*/
export default class Toolbar extends Component<Props> {
export default class Toolbar extends Component<IProps> {
/**
* Implements React's {@link Component#render()}.
*

View File

@@ -2,38 +2,38 @@ import React, { useCallback } from 'react';
import Icon from '../base/icons/components/Icon';
type Props = {
interface IProps {
/**
* 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,
/**
* Click handler.
*/
onClick: Function,
disabled?: boolean;
/**
* Button icon.
*/
icon: Object,
icon: Function;
/**
* Click handler.
*/
onClick: (e?: React.MouseEvent) => void;
/**
* Whether or not the button is toggled.
*/
toggled?: boolean
toggled?: boolean;
}
const ToolbarButton = ({
@@ -43,7 +43,7 @@ const ToolbarButton = ({
onClick,
icon,
toggled = false
}: Props) => {
}: IProps) => {
const onKeyPress = useCallback(event => {
if (event.key === 'Enter' || event.key === ' ') {
event.preventDefault();

View File

@@ -1,15 +1,16 @@
// @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 type { Props } from '../base/toolbox/components/AbstractButton';
import { IProps } 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}.
*/
@@ -18,12 +19,12 @@ type State = {
/**
* Whether video is available is not.
*/
videoAvailable: boolean,
videoAvailable: boolean;
/**
* Whether video is muted or not.
*/
videoMuted: boolean
videoMuted: boolean;
};
/**
@@ -119,40 +120,34 @@ 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) { // eslint-disable-line no-unused-vars
_setVideoMuted(_videoMuted: boolean) {
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 }) {
_videoAvailabilityListener({ available }: { available: boolean; }) {
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 }) {
_videoMutedListener({ muted }: { muted: boolean; }) {
this.setState({ videoMuted: muted });
}
_onClick: () => {};
/**
* Handles clicking / pressing the button, and toggles the video mute state
* accordingly.
@@ -173,11 +168,13 @@ 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,14 +1,11 @@
// @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')));
() => ReactDOM.unmountComponentAtNode(document.getElementById('react') ?? document.body));

View File

@@ -1,4 +1,3 @@
/* eslint-disable lines-around-comment */
import { setRoom } from '../base/conference/actions';
import {
configWillLoad,
@@ -13,6 +12,7 @@ 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,14 +20,11 @@ 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';
@@ -55,7 +52,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 || !location.host) {
if (!location?.host) {
const defaultLocation = parseURIString(getDefaultURL(getState));
if (location) {
@@ -140,10 +137,14 @@ 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 || !location.host) {
if (!location?.host) {
const defaultLocation = parseURIString(getDefaultURL(getState));
if (location) {

View File

@@ -5,8 +5,6 @@ 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,4 +1,5 @@
import '../base/devices/reducer';
import '../base/premeeting/reducer';
import '../base/tooltip/reducer';
import '../e2ee/reducer';
import '../face-landmarks/reducer';

View File

@@ -20,6 +20,7 @@ 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';
@@ -110,6 +111,7 @@ 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, passwordRequired }
const { error = { recoverable: undefined }, passwordRequired }
= getState()['features/base/connection'];
passwordRequired
&& dispatch(
connectionFailed(
passwordRequired, // @ts-ignore
passwordRequired,
set(error, 'recoverable', false) as any));
};
}

View File

@@ -1,4 +1,2 @@
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'; // @ts-ignore
waitForOwner } from './actions.native';
import { LoginDialog, WaitForOwnerDialog } from './components';
/**

View File

@@ -88,7 +88,7 @@ class ColorSchemeRegistry {
_applyColorScheme(
stateful: IStateful,
componentName: string,
style: StyleType): StyleType {
style: StyleType | null): StyleType {
let schemedStyle: any;
if (Array.isArray(style)) {
@@ -115,8 +115,8 @@ class ColorSchemeRegistry {
// The value is another style object, we apply the same
// transformation recursively.
schemedStyle[styleName]
= this._applyColorScheme( // @ts-ignore
stateful, componentName, styleValue);
= this._applyColorScheme(
stateful, componentName, styleValue as StyleType);
} else if (typeof styleValue === 'function') {
// The value is a function, which indicates that it's a
// dynamic, schemed color we need to resolve.

View File

@@ -98,34 +98,32 @@ function _addConferenceListeners(conference: IJitsiConference, dispatch: IStore[
JitsiConferenceEvents.AUTH_STATUS_CHANGED,
(authEnabled: boolean, authLogin: string) => dispatch(authStatusChanged(authEnabled, authLogin)));
conference.on(
JitsiConferenceEvents.CONFERENCE_FAILED, // @ts-ignore
(...args: any[]) => dispatch(conferenceFailed(conference, ...args)));
JitsiConferenceEvents.CONFERENCE_FAILED,
(err: string, ...args: any[]) => dispatch(conferenceFailed(conference, err, ...args)));
conference.on(
JitsiConferenceEvents.CONFERENCE_JOINED, // @ts-ignore
(...args: any[]) => dispatch(conferenceJoined(conference, ...args)));
JitsiConferenceEvents.CONFERENCE_JOINED,
(..._args: any[]) => dispatch(conferenceJoined(conference)));
conference.on(
JitsiConferenceEvents.CONFERENCE_UNIQUE_ID_SET, // @ts-ignore
(...args: any[]) => dispatch(conferenceUniqueIdSet(conference, ...args)));
JitsiConferenceEvents.CONFERENCE_UNIQUE_ID_SET,
(..._args: any[]) => dispatch(conferenceUniqueIdSet(conference)));
conference.on(
JitsiConferenceEvents.CONFERENCE_JOIN_IN_PROGRESS, // @ts-ignore
(...args: any[]) => dispatch(conferenceJoinInProgress(conference, ...args)));
JitsiConferenceEvents.CONFERENCE_JOIN_IN_PROGRESS,
(..._args: any[]) => dispatch(conferenceJoinInProgress(conference)));
conference.on(
JitsiConferenceEvents.CONFERENCE_LEFT,
(...args: any[]) => {
(..._args: any[]) => {
dispatch(conferenceTimestampChanged(0));
// @ts-ignore
dispatch(conferenceLeft(conference, ...args));
dispatch(conferenceLeft(conference));
});
conference.on(JitsiConferenceEvents.SUBJECT_CHANGED, // @ts-ignore
(...args: any[]) => dispatch(conferenceSubjectChanged(...args)));
conference.on(JitsiConferenceEvents.SUBJECT_CHANGED,
(subject: string) => dispatch(conferenceSubjectChanged(subject)));
conference.on(JitsiConferenceEvents.CONFERENCE_CREATED_TIMESTAMP, // @ts-ignore
(...args: any[]) => dispatch(conferenceTimestampChanged(...args)));
conference.on(JitsiConferenceEvents.CONFERENCE_CREATED_TIMESTAMP,
(timestamp: number) => dispatch(conferenceTimestampChanged(timestamp)));
conference.on(
JitsiConferenceEvents.KICKED, // @ts-ignore
(...args: any[]) => dispatch(kickedOut(conference, ...args)));
JitsiConferenceEvents.KICKED,
(participant: any) => dispatch(kickedOut(conference, participant)));
conference.on(
JitsiConferenceEvents.PARTICIPANT_KICKED,
@@ -136,8 +134,8 @@ function _addConferenceListeners(conference: IJitsiConference, dispatch: IStore[
(jitsiParticipant: IJitsiParticipant) => dispatch(participantSourcesUpdated(jitsiParticipant)));
conference.on(
JitsiConferenceEvents.LOCK_STATE_CHANGED, // @ts-ignore
(...args: any[]) => dispatch(lockStateChanged(conference, ...args)));
JitsiConferenceEvents.LOCK_STATE_CHANGED,
(locked: boolean) => dispatch(lockStateChanged(conference, locked)));
// Dispatches into features/base/media follow:
@@ -220,12 +218,12 @@ function _addConferenceListeners(conference: IJitsiConference, dispatch: IStore[
});
conference.on(
JitsiConferenceEvents.ENDPOINT_MESSAGE_RECEIVED, // @ts-ignore
(...args: any[]) => dispatch(endpointMessageReceived(...args)));
JitsiConferenceEvents.ENDPOINT_MESSAGE_RECEIVED,
(participant: Object, json: Object) => dispatch(endpointMessageReceived(participant, json)));
conference.on(
JitsiConferenceEvents.NON_PARTICIPANT_MESSAGE_RECEIVED, // @ts-ignore
(...args: any[]) => dispatch(nonParticipantMessageReceived(...args)));
JitsiConferenceEvents.NON_PARTICIPANT_MESSAGE_RECEIVED,
(id: string, json: Object) => dispatch(nonParticipantMessageReceived(id, json)));
conference.on(
JitsiConferenceEvents.USER_JOINED,
@@ -234,15 +232,15 @@ function _addConferenceListeners(conference: IJitsiConference, dispatch: IStore[
JitsiConferenceEvents.USER_LEFT,
(_id: string, user: any) => commonUserLeftHandling({ dispatch }, conference, user));
conference.on(
JitsiConferenceEvents.USER_ROLE_CHANGED, // @ts-ignore
(...args: any[]) => dispatch(participantRoleChanged(...args)));
JitsiConferenceEvents.USER_ROLE_CHANGED,
(id: string, role: string) => dispatch(participantRoleChanged(id, role)));
conference.on(
JitsiConferenceEvents.USER_STATUS_CHANGED, // @ts-ignore
(...args: any[]) => dispatch(participantPresenceChanged(...args)));
JitsiConferenceEvents.USER_STATUS_CHANGED,
(id: string, presence: string) => dispatch(participantPresenceChanged(id, presence)));
conference.on(
JitsiE2ePingEvents.E2E_RTT_CHANGED, // @ts-ignore
(...args: any[]) => dispatch(e2eRttChanged(...args)));
JitsiE2ePingEvents.E2E_RTT_CHANGED,
(participant: Object, rtt: number) => dispatch(e2eRttChanged(participant, rtt)));
conference.on(
JitsiConferenceEvents.BOT_TYPE_CHANGED,
@@ -633,7 +631,7 @@ export function endConference() {
* participant: JitsiParticipant
* }}
*/
export function kickedOut(conference: Object, participant: Object) {
export function kickedOut(conference: IJitsiConference, participant: Object) {
return {
type: KICKED_OUT,
conference,
@@ -673,7 +671,7 @@ export function leaveConference() {
* locked: boolean
* }}
*/
export function lockStateChanged(conference: Object, locked: boolean) {
export function lockStateChanged(conference: IJitsiConference, locked: boolean) {
return {
type: LOCK_STATE_CHANGED,
conference,
@@ -692,7 +690,7 @@ export function lockStateChanged(conference: Object, locked: boolean) {
* json: Object
* }}
*/
export function nonParticipantMessageReceived(id: String, json: Object) {
export function nonParticipantMessageReceived(id: string, json: Object) {
return {
type: NON_PARTICIPANT_MESSAGE_RECEIVED,
id,

View File

@@ -292,7 +292,7 @@ export function getVisitorOptions(stateful: IStateful, params: Array<string>) {
const config = toState(stateful)['features/base/config'];
if (!config || !config.hosts) {
if (!config?.hosts) {
logger.warn('Wrong configuration, missing hosts.');
return;
@@ -300,7 +300,8 @@ export function getVisitorOptions(stateful: IStateful, params: Array<string>) {
if (!vnode) {
// this is redirecting back to main, lets restore config
// no point of updating disableFocus, we can skip the initial iq to jicofo
// 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
if (config.oldConfig && username) {
return {
hosts: {
@@ -310,6 +311,7 @@ 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
@@ -326,6 +328,7 @@ export function getVisitorOptions(stateful: IStateful, params: Array<string>) {
},
focusUserJid: config.focusUserJid,
bosh: config.bosh,
p2p: config.p2p,
websocket: config.websocket
};
@@ -341,6 +344,10 @@ 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

@@ -67,7 +67,7 @@ import logger from './logger';
/**
* Handler for before unload event.
*/
let beforeUnloadHandler: Function | undefined;
let beforeUnloadHandler: (() => void) | undefined;
/**
* Implements the middleware of the feature base/conference.
@@ -289,7 +289,12 @@ function _conferenceJoined({ dispatch, getState }: IStore, next: Function, actio
dispatch(conferenceWillLeave(conference));
};
// @ts-ignore
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 }));
}
window.addEventListener(disableBeforeUnloadHandlers ? 'unload' : 'beforeunload', beforeUnloadHandler);
if (requireDisplayName
@@ -507,7 +512,6 @@ function _removeUnloadHandler(getState: IStore['getState']) {
if (typeof beforeUnloadHandler !== 'undefined') {
const { disableBeforeUnloadHandlers = false } = getState()['features/base/config'];
// @ts-ignore
window.removeEventListener(disableBeforeUnloadHandlers ? 'unload' : 'beforeunload', beforeUnloadHandler);
beforeUnloadHandler = undefined;
}

View File

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

View File

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

View File

@@ -18,6 +18,7 @@ type ToolbarButtons = 'camera' |
'participants-pane' |
'profile' |
'raisehand' |
'reactions' |
'recording' |
'security' |
'select-background' |
@@ -169,6 +170,7 @@ export interface IConfig {
}>;
callDisplayName?: string;
callFlowsEnabled?: boolean;
callHandle?: string;
callStatsConfigParams?: {
additionalIDs?: {
customerID?: string;
@@ -190,6 +192,7 @@ export interface IConfig {
};
callStatsID?: string;
callStatsSecret?: string;
callUUID?: string;
channelLastN?: number;
chromeExtensionBanner?: {
chromeExtensionsInfo?: Array<{ id: string; path: string; }>;
@@ -405,6 +408,7 @@ 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', 'tileview' ];
export const VISITORS_MODE_BUTTONS = [ 'chat', 'hangup', 'raisehand', 'settings', 'tileview' ];
/**
* The set of feature flags.

View File

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

View File

@@ -84,6 +84,7 @@ 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?: Function;
renderFooter?: () => React.ReactNode;
/**
* Function to render a bottom sheet header element, if necessary.
@@ -109,9 +109,7 @@ class BottomSheet extends PureComponent<Props> {
} = this.props;
return (
<SlidingView // @ts-ignore
accessibilityRole = 'menu'
accessibilityViewIsModal = { true }
<SlidingView
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-ignore
// @ts-expect-error
import { randomInt } from '@jitsi/js-utils/random';
import React, { Component } from 'react';
import { WithTranslation } from 'react-i18next';
@@ -11,7 +11,6 @@ import { isFatalJitsiConnectionError } from '../../../lib-jitsi-meet/functions.n
import { hideDialog } from '../../actions';
import logger from '../../logger';
// @ts-ignore
import ConfirmDialog from './ConfirmDialog';
@@ -39,9 +38,7 @@ interface IPageReloadDialogState {
* Shows a warning message and counts down towards the re-load.
*/
class PageReloadDialog extends Component<IPageReloadDialogProps, IPageReloadDialogState> {
// @ts-ignore
_interval: IntervalID;
_interval?: number;
_timeoutSeconds: number;
/**
@@ -105,7 +102,7 @@ class PageReloadDialog extends Component<IPageReloadDialogProps, IPageReloadDial
_onCancel() {
const { dispatch } = this.props;
clearInterval(this._interval);
clearInterval(this._interval ?? 0);
dispatch(appNavigate(undefined));
return true;
@@ -145,7 +142,7 @@ class PageReloadDialog extends Component<IPageReloadDialogProps, IPageReloadDial
_onReloadNow() {
const { dispatch } = this.props;
clearInterval(this._interval);
clearInterval(this._interval ?? 0);
dispatch(reloadNow());
return true;
@@ -200,8 +197,6 @@ 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-ignore
// @ts-expect-error
import Bourne from '@hapi/bourne';
// eslint-disable-next-line lines-around-comment
// @ts-ignore
// @ts-expect-error
import { jitsiLocalStorage } from '@jitsi/js-utils/jitsi-local-storage';
import { browser } from '../lib-jitsi-meet';

View File

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

View File

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

View File

@@ -1,10 +1,9 @@
// @ts-ignore
// @ts-expect-error
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,7 +1,7 @@
import { IStateful } from '../app/types';
import { ConnectionFailedError } from '../connection/actions.any';
import { toState } from '../redux/functions';
// @ts-ignore
import JitsiMeetJS from './_';
@@ -91,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) {
export function isFatalJitsiConnectionError(error: Error | string | ConnectionFailedError) {
if (typeof error !== 'string') {
error = error.name; // eslint-disable-line no-param-reassign
}

View File

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

View File

@@ -1,6 +1,5 @@
// 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,7 +6,6 @@ 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 || !conference.isCallstatsEnabled()) {
if (!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-ignore
// @ts-expect-error
import { format } from 'util';
// Some code adapted from https://github.com/houserater/react-native-lumberjack

View File

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

View File

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

View File

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

View File

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

View File

@@ -321,7 +321,7 @@ class Video extends Component<IProps> {
* @returns {void}
*/
_attachTrack(videoTrack?: Partial<ITrack>) {
if (!videoTrack || !videoTrack.jitsiTrack) {
if (!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-ignore
// @ts-expect-error
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,10 +26,8 @@ export default class NetworkInfoService extends EventEmitter {
*/
static _convertNetInfoState(netInfoState: NetInfoState): NetworkInfo {
return {
// @ts-ignore
isOnline: netInfoState.isInternetReachable,
isOnline: Boolean(netInfoState.isInternetReachable),
// @ts-ignore
details: netInfoState.details,
networkType: netInfoState.type
};
@@ -51,8 +49,7 @@ export default class NetworkInfoService extends EventEmitter {
*/
start() {
this._subscription = NetInfo.addEventListener(netInfoState => {
// @ts-ignore
this.emit(ONLINE_STATE_CHANGED_EVENT, NetworkInfoService._convertNetInfoState(netInfoState));
super.emit(ONLINE_STATE_CHANGED_EVENT, NetworkInfoService._convertNetInfoState(netInfoState));
});
}
@@ -64,8 +61,6 @@ 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;
cellularGeneration?: NetInfoCellularGeneration | null;
/**
* 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-ignore
// @ts-expect-error
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 || !name.trim()) {
if (!name?.trim()) {
return undefined;
}

View File

@@ -6,6 +6,7 @@ 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';
/**
@@ -258,7 +259,16 @@ class Popover extends Component<IProps, IState> {
'aria-labelledby': headingId,
'aria-label': !headingId && headingLabel ? headingLabel : undefined
}}
returnFocus = { true }>
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
}>
{this._renderContent()}
</ReactFocusLock>
</DialogPortal>
@@ -304,7 +314,8 @@ class Popover extends Component<IProps, IState> {
&& !this.props.overflowDrawer
&& this._contextMenuRef
&& this._contextMenuRef.contains
&& !this._contextMenuRef.contains(event.target as Node)) {
&& !this._contextMenuRef.contains(event.target as Node)
&& !this._containerRef?.current?.contains(event.target as Node)) {
this._onHideDialog();
}
}

View File

@@ -0,0 +1,9 @@
/**
* 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

@@ -0,0 +1,17 @@
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,9 +12,8 @@ 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 {
@@ -58,6 +57,11 @@ interface IProps {
*/
showDeviceStatus: boolean;
/**
* If should show unsafe room warning when joining.
*/
showUnsafeRoomWarning?: boolean;
/**
* The 'Skip prejoin' button to be rendered (if any).
*/
@@ -163,6 +167,7 @@ const PreMeetingScreen = ({
children,
className,
showDeviceStatus,
showUnsafeRoomWarning,
skipPrejoinButton,
title,
videoMuted,
@@ -193,6 +198,7 @@ const PreMeetingScreen = ({
{children}
{_buttons.length && <Toolbox toolbarButtons = { _buttons } />}
{skipPrejoinButton}
{showUnsafeRoomWarning && <UnsafeRoomWarning />}
{showDeviceStatus && <DeviceStatus />}
</div>
</div>

View File

@@ -0,0 +1,54 @@
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

@@ -0,0 +1,33 @@
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

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

View File

@@ -1,5 +1,2 @@
/* 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 { getFixedPlatformStyle } from '../../../styles/functions.web';
import { StyleType, 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() {
// @ts-ignore
const _style = getFixedPlatformStyle(this.props.style);
// eslint-disable-next-line react/prop-types
const _style = getFixedPlatformStyle(this.props.style as StyleType);
return React.createElement('span', {
...this.props,

View File

@@ -168,10 +168,10 @@ class Watermarks extends Component<IProps, State> {
maxWidth: 140,
maxHeight: 70,
position: _logoLink ? 'static' : 'absolute'
};
} as const;
reactElement = (<div
className = { className } // @ts-ignore
className = { className }
style = { style } />);
if (_logoLink) {

View File

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

View File

@@ -1,5 +1,3 @@
/* eslint-disable lines-around-comment */
import { connect } from 'react-redux';
import { IReduxState } from '../../../../app/types';
@@ -7,9 +5,7 @@ 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';

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