Compare commits

...

35 Commits

Author SHA1 Message Date
Saúl Ibarra Corretgé
7bf3e7df1d fix(rn,polyfills) remove no longer needed polyfill
Fixes: https://github.com/jitsi/jitsi-meet/issues/13807
2023-09-28 12:23:09 +02:00
damencho
f9ac965e18 feat: Updates for jwt when room claim is not required.
This allows mod_token_verification to be used with token missing room claim (firebase) and jitsi tokens with the claim.
2023-09-27 19:29:16 -05:00
Gabriel Borlea
d70412166c chore(deps) lib-jitsi-meet@latest
https://github.com/jitsi/lib-jitsi-meet/compare/v1694.0.0+a0145343...v1695.0.0+51c2187b
2023-09-27 16:20:19 +03:00
Дамян Минков
a843406cb0 feat: Check jwt expiration and redirects to auth url if any. (#13879)
* feat: Check jwt expiration and redirects to auth url if any.

It may happen that the jwt had expired while being in the meeting and there is a network issue requiring to reload.

* squash: Fixes lint error.

* squash: Fixes comments.
2023-09-27 07:40:07 -05:00
AHMAD KADRI
58115477a2 Improve accessibility in breakout rooms list (#13669)
Improve accessibility in breakout rooms list
2023-09-27 12:13:03 +03:00
Hristo Terezov
e1dc573c3c fix(GUM):set deviceId only when the device exists 2023-09-26 12:53:08 -05:00
Hristo Terezov
c025102511 feat(devices): Filter MS Teams Audio device 2023-09-26 12:53:08 -05:00
Gabriel Borlea
54d052de73 chore: update js-utils with new ua-parser (#13877)
* chore: update js-utils with new ua-parser

* chore(deps) lib-jitsi-meet@latest
2023-09-26 19:31:26 +03:00
Javier García
7e633f0136 fix: Also check single quote on jitsi-meet-tokens install (#13869)
Fixes #13768.
2023-09-26 11:25:38 -05:00
Gabriel Borlea
4b4bc1c823 chore(deps) lib-jitsi-meet@latest (#13871)
https://github.com/jitsi/lib-jitsi-meet/compare/v1691.0.0+255d8f49...v1693.0.0+c3a086f8
2023-09-25 18:21:58 +03:00
Saúl Ibarra Corretgé
767e23f34c fix(android,deps) update GMS native dependencies
Should fix this error:

~~~
Fatal Exception: java.lang.IllegalArgumentException: org.jitsi.meet: Targeting S+ (version 31 and above) requires that one of FLAG_IMMUTABLE or FLAG_MUTABLE be specified when creating a PendingIntent.
Strongly consider using FLAG_IMMUTABLE, only use FLAG_MUTABLE if some functionality depends on the PendingIntent being mutable, e.g. if it needs to be used with inline replies or bubbles.
       at android.app.PendingIntent.checkFlags(PendingIntent.java:402)
       at android.app.PendingIntent.getActivityAsUser(PendingIntent.java:485)
       at android.app.PendingIntent.getActivity(PendingIntent.java:471)
       at android.app.PendingIntent.getActivity(PendingIntent.java:435)
       at com.google.android.gms.common.GoogleApiAvailabilityLight.getErrorResolutionPendingIntent(com.google.android.gms:play-services-basement@@17.5.0:25)
       at com.google.android.gms.common.GoogleApiAvailabilityLight.getErrorResolutionPendingIntent(com.google.android.gms:play-services-basement@@17.5.0:21)
       at com.google.android.gms.common.GoogleApiAvailability.getErrorResolutionPendingIntent(com.google.android.gms:play-services-base@@17.5.0:170)
       at com.google.android.gms.common.GoogleApiAvailability.getErrorResolutionPendingIntent(com.google.android.gms:play-services-base@@17.5.0:173)
       at com.google.android.gms.common.GoogleApiAvailability.zaa(com.google.android.gms:play-services-base@@17.5.0:112)
       at com.google.android.gms.common.api.internal.GoogleApiManager.zaa(com.google.android.gms:play-services-base@@17.5.0:252)
       at com.google.android.gms.common.api.internal.GoogleApiManager$zaa.zaa(com.google.android.gms:play-services-base@@17.5.0:109)
       at com.google.android.gms.common.api.internal.GoogleApiManager$zaa.onConnectionFailed(com.google.android.gms:play-services-base@@17.5.0:75)
       at com.google.android.gms.common.api.internal.GoogleApiManager$zaa.zai(com.google.android.gms:play-services-base@@17.5.0:263)
       at com.google.android.gms.common.api.internal.GoogleApiManager$zaa.zaa(com.google.android.gms:play-services-base@@17.5.0:133)
       at com.google.android.gms.common.api.internal.GoogleApiManager.handleMessage(com.google.android.gms:play-services-base@@17.5.0:164)
       at android.os.Handler.dispatchMessage(Handler.java:102)
       at android.os.Looper.loopOnce(Looper.java:240)
       at android.os.Looper.loop(Looper.java:351)
       at android.os.HandlerThread.run(HandlerThread.java:67)
~~~
2023-09-25 16:46:42 +02:00
Saúl Ibarra Corretgé
b003d28cc5 fix(ios) remove duplicate dependency
We get JitsiWebRTC transitively through the react-native-webrtc Pod now.
2023-09-25 16:44:08 +02:00
Saúl Ibarra Corretgé
91c8e9bd86 chore(deps,rnsdk) add missing dependency 2023-09-25 17:11:21 +03:00
Saúl Ibarra Corretgé
1f52c0b49f chore(deps,rnsdk) sync dependencies 2023-09-25 17:11:21 +03:00
Calin-Teodor
16fd4d4411 sdk(react-native-sdk): update script to check if dep versions are valid 2023-09-25 14:03:04 +03:00
Saúl Ibarra Corretgé
b8a669ad21 feat(android) disable ConnectionService by default
Our app has had it disabled for quite a while, it makes sense the SDKs
do that too.

Fixes: https://github.com/jitsi/jitsi-meet/issues/13800
2023-09-23 22:01:28 +02:00
damencho
f0cb33a627 fix: Fixes missing import in wait for host module. 2023-09-21 14:45:12 -05:00
damencho
b5b7019325 fix: Adds check for missing main_room. 2023-09-21 12:17:53 -05:00
damencho
7ccd68eb18 feat: Introduces passing state to the token authUrl.
Fixes jitsi/jitsi-meet-electron#902.
2023-09-21 12:17:53 -05:00
damencho
44b0ac57eb feat: Updates base64-js dependency. 2023-09-21 12:17:53 -05:00
damencho
a411b7c969 fix: Fixes check for health check room. 2023-09-21 12:17:53 -05:00
damencho
fc8ce532f6 feat: Hides any error from the UI for the DialIn info app.
If the conference mapper return an error we show it on deeplinking page. In case the conf mapper receives non authenticated request it may return an error and this is normal so hide it from that page.
2023-09-21 12:17:53 -05:00
damencho
ef56b3c5b6 feat: Adds an event for host arrived. 2023-09-21 12:17:53 -05:00
Saúl Ibarra Corretgé
37e13804a5 fix(ios) fix compilation with Xcode 14.3
Fixes: https://github.com/jitsi/jitsi-meet/issues/13274
2023-09-21 19:17:15 +02:00
Saúl Ibarra Corretgé
8b209b3c6e fix(external-api) add policy to support the Compute Pressure API
https://w3c.github.io/compute-pressure/#policy-control
2023-09-21 17:15:36 +02:00
Mihaela Dumitru
cb26042d08 fix(virtual-background) display current settings (#13857) 2023-09-21 15:45:17 +03:00
Javier
2952d1cde8 Fix disable virtual background feature, now hides the feature everywhere 2023-09-20 09:05:10 -05:00
Licaon_Kter
8a7f456560 feat(android) add Fastlane metadata
Fix https://github.com/jitsi/jitsi-meet/issues/11786
2023-09-20 11:46:56 +02:00
Thomas Egebrand Gram
f74b6cd82f fix (mobile-layout) change "vh" to "dvh" for all layouts (#13840)
* Convert all vh units to dvh; fixing layout for mobile browsers such as Chrome for Android.
2023-09-20 12:19:54 +03:00
Calinteodor
d04515c35a feat(prejoin/native): fixed screen header hooks warning (#13845)
* feat(prejoin/native): fixed screen header hooks warning
2023-09-20 11:40:28 +03:00
Jaya Allamsetty
2aca0ce110 chore(deps) lib-jitsi-meet@latest
https://github.com/jitsi/lib-jitsi-meet/compare/v1689.0.0+0d5c73d1...v1691.0.0+255d8f49
2023-09-19 18:24:02 -04:00
Calinteodor
d0e49b27a1 feat(app/native): rework appNavigate so callkit video button does not end the call (#13814)
* feat(app/native): rework appNavigate so callkit video button does not end the call
2023-09-19 20:47:29 +03:00
Jaya Allamsetty
d97c365aed chore(deps) lib-jitsi-meet@latest
https://github.com/jitsi/lib-jitsi-meet/compare/v1687.0.0+cafe30d7...v1689.0.0+0d5c73d1
2023-09-19 12:54:18 -04:00
damencho
8304e77a04 chore(deps) lib-jitsi-meet@latest
https://github.com/jitsi/lib-jitsi-meet/compare/v1686.0.0+1b5830f1...v1687.0.0+cafe30d7
2023-09-14 16:02:29 +02:00
Saúl Ibarra Corretgé
b1db315582 fix(authentication) fix moderator logout 2023-09-14 13:44:08 +02:00
84 changed files with 1087 additions and 359 deletions

View File

@@ -82,14 +82,14 @@ dependencies {
if (!rootProject.ext.libreBuild) {
// Sync with react-native-google-signin
implementation 'com.google.android.gms:play-services-auth:19.2.0'
implementation 'com.google.android.gms:play-services-auth:20.5.0'
// Firebase
// - Crashlytics
// - Dynamic Links
implementation 'com.google.firebase:firebase-analytics:17.5.0'
implementation 'com.google.firebase:firebase-crashlytics:17.2.1'
implementation 'com.google.firebase:firebase-dynamic-links:19.1.0'
implementation 'com.google.firebase:firebase-analytics:21.3.0'
implementation 'com.google.firebase:firebase-crashlytics:18.4.3'
implementation 'com.google.firebase:firebase-dynamic-links:21.1.0'
}
implementation project(':sdk')

View File

@@ -152,7 +152,6 @@ public class MainActivity extends JitsiMeetActivity {
= new JitsiMeetConferenceOptions.Builder()
.setServerURL(buildURL(defaultURL))
.setFeatureFlag("welcomepage.enabled", true)
.setFeatureFlag("call-integration.enabled", false)
.setFeatureFlag("resolution", 360)
.setFeatureFlag("server-url-change.enabled", !configurationByRestrictions)
.build();

View File

@@ -11,8 +11,8 @@ buildscript {
}
dependencies {
classpath 'com.android.tools.build:gradle:7.3.1'
classpath 'com.google.gms:google-services:4.3.14'
classpath 'com.google.firebase:firebase-crashlytics-gradle:2.9.2'
classpath 'com.google.gms:google-services:4.4.0'
classpath 'com.google.firebase:firebase-crashlytics-gradle:2.9.9'
}
}

View File

@@ -65,7 +65,11 @@ import {
updateDeviceList
} from './react/features/base/devices/actions.web';
import {
areDevicesDifferent,
filterIgnoredDevices,
flattenAvailableDevices,
getDefaultDeviceId,
logDevices,
setAudioOutputDeviceId
} from './react/features/base/devices/functions.web';
import {
@@ -2241,19 +2245,28 @@ export default {
* @returns {Promise}
*/
async _onDeviceListChanged(devices) {
const oldDevices = APP.store.getState()['features/base/devices'].availableDevices;
const localAudio = getLocalJitsiAudioTrack(APP.store.getState());
const localVideo = getLocalJitsiVideoTrack(APP.store.getState());
const state = APP.store.getState();
const { filteredDevices, ignoredDevices } = filterIgnoredDevices(devices);
const oldDevices = state['features/base/devices'].availableDevices;
APP.store.dispatch(updateDeviceList(devices));
if (!areDevicesDifferent(flattenAvailableDevices(oldDevices), filteredDevices)) {
return Promise.resolve();
}
logDevices(ignoredDevices, 'Ignored devices on device list changed:');
const localAudio = getLocalJitsiAudioTrack(state);
const localVideo = getLocalJitsiVideoTrack(state);
APP.store.dispatch(updateDeviceList(filteredDevices));
// Firefox users can choose their preferred device in the gUM prompt. In that case
// we should respect that and not attempt to switch to the preferred device from
// our settings.
const newLabelsOnly = mediaDeviceHelper.newDeviceListAddedLabelsOnly(oldDevices, devices);
const newLabelsOnly = mediaDeviceHelper.newDeviceListAddedLabelsOnly(oldDevices, filteredDevices);
const newDevices
= mediaDeviceHelper.getNewMediaDevicesAfterDeviceListChanged(
devices,
filteredDevices,
localVideo,
localAudio,
newLabelsOnly);
@@ -2386,7 +2399,7 @@ export default {
return Promise.all(promises)
.then(() => {
APP.UI.onAvailableDevicesChanged(devices);
APP.UI.onAvailableDevicesChanged(filteredDevices);
});
},

View File

@@ -1441,6 +1441,31 @@ var config = {
// dialInConfCodeUrl is the conference mapper converting a meeting id to a PIN used for dial-in
// or the other way around (more info in resources/cloud-api.swagger)
// You can use external service for authentication that will redirect back passing a jwt token
// You can use tokenAuthUrl config to point to a URL of such service.
// The URL for the service supports few params which will be filled in by the code.
// tokenAuthUrl:
// 'https://myservice.com/auth/{room}?code_challenge_method=S256&code_challenge={code_challenge}&state={state}'
// Supported parameters in tokenAuthUrl:
// {room} - will be replaced with the room name
// {code_challenge} - (A web only). A oauth 2.0 code challenge that will be sent to the service. See:
// https://datatracker.ietf.org/doc/html/rfc7636. The code verifier will be saved in the sessionStorage
// under key: 'code_verifier'.
// {state} - A json with the current state before redirecting. Keys that are included in the state:
// - room (The current room name as shown in the address bar)
// - roomSafe (the backend safe room name to use (lowercase), that is passed to the backend)
// - tenant (The tenant if any)
// - config.xxx (all config overrides)
// - interfaceConfig.xxx (all interfaceConfig overrides)
// - ios=true (in case ios mobile app is used)
// - android=true (in case android mobile app is used)
// - electron=true (when web is loaded in electron app)
// If there is a logout service you can specify its URL with:
// tokenLogoutUrl: 'https://myservice.com/logout'
// You can enable tokenAuthUrlAutoRedirect which will detect that you have logged in successfully before
// and will automatically redirect to the token service to get the token for the meeting.
// tokenAuthUrlAutoRedirect: false
// List of undocumented settings used in jitsi-meet
/**
_immediateReloadThreshold
@@ -1460,9 +1485,6 @@ var config = {
peopleSearchQueryTypes
peopleSearchUrl
requireDisplayName
tokenAuthUrl
tokenAuthUrlAutoRedirect
tokenLogoutUrl
*/
/**

View File

@@ -154,17 +154,17 @@ $reactionCount: 20;
}
70% {
transform: translate(40px, -70vh) scale(1.5);
transform: translate(40px, -70dvh) scale(1.5);
opacity: 1;
}
75% {
transform: translate(40px, -70vh) scale(1.5);
transform: translate(40px, -70dvh) scale(1.5);
opacity: 1;
}
100% {
transform: translate(140px, -50vh) scale(1);
transform: translate(140px, -50dvh) scale(1);
opacity: 0;
}
}
@@ -187,17 +187,17 @@ $reactionCount: 20;
}
70% {
transform: translate(#{$topX}px, -#{$topY}vh) scale(1.5);
transform: translate(#{$topX}px, -#{$topY}dvh) scale(1.5);
opacity: 1;
}
75% {
transform: translate(#{$topX}px, -#{$topY}vh) scale(1.5);
transform: translate(#{$topX}px, -#{$topY}dvh) scale(1.5);
opacity: 1;
}
100% {
transform: translate(#{$bottomX}px, -#{$bottomY}vh) scale(1);
transform: translate(#{$bottomX}px, -#{$bottomY}dvh) scale(1);
opacity: 0;
}
}

View File

@@ -266,10 +266,10 @@
#avatarContainer {
border-radius: 50%;
display: inline-block;
height: 50vh;
margin-top: 25vh;
height: 50dvh;
margin-top: 25dvh;
overflow: hidden;
width: 50vh;
width: 50dvh;
#avatar {
height: 100%;

View File

@@ -10,7 +10,7 @@ body.welcome-page {
flex-direction: column;
font-family: $welcomePageFontFamily;
justify-content: space-between;
min-height: 100vh;
min-height: 100dvh;
position: relative;
.header {

View File

@@ -1,6 +1,6 @@
.deep-linking-mobile {
background-color: #fff;
height: 100vh;
height: 100dvh;
overflow: auto;
position: relative;
width: 100vw;

View File

@@ -39,7 +39,7 @@ case "$1" in
echo "Application secret is mandatory"
fi
# Not allowed unix special characters in secret: /, \, ", ', `
if echo "$RET" | grep -q '[/\\\"\`]' ; then
if echo "$RET" | grep -q "[/\\\"\`\']" ; then
echo "Application secret contains invalid characters: /, \\, \", ', \`"
exit 1
fi

View File

@@ -46,7 +46,6 @@ target 'JitsiMeetSDK' do
pod 'CocoaLumberjack', '3.7.2'
pod 'ObjectiveDropboxOfficial', '6.2.3'
pod 'JitsiWebRTC', '~> 111.0.0'
end
target 'JitsiMeetSDKLite' do
@@ -86,6 +85,7 @@ post_install do |installer|
target.build_configurations.each do |config|
config.build_settings['SUPPORTS_MACCATALYST'] = 'NO'
config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = '12.4'
config.build_settings['OTHER_SWIFT_FLAGS'] = '$(inherited) -no-verify-emitted-module-interface'
end
end
end

View File

@@ -503,7 +503,6 @@ DEPENDENCIES:
- Firebase/DynamicLinks (~> 8.0)
- "giphy-react-native-sdk (from `../node_modules/@giphy/react-native-sdk`)"
- glog (from `../node_modules/react-native/third-party-podspecs/glog.podspec`)
- JitsiWebRTC (~> 111.0.0)
- ObjectiveDropboxOfficial (= 6.2.3)
- RCT-Folly (from `../node_modules/react-native/third-party-podspecs/RCT-Folly.podspec`)
- RCTRequired (from `../node_modules/react-native/Libraries/RCTRequired`)
@@ -787,6 +786,6 @@ SPEC CHECKSUMS:
RNWatch: fd30ca40a5b5ef58dcbc195638e68219bc455236
Yoga: 7f5ad94937ba3fc58c151ad1b7bbada2c275b28e
PODFILE CHECKSUM: e3579df5272b8b697c9fdc0e55aa0845b189c4dd
PODFILE CHECKSUM: 90720aee51cf2cab2e12611a28dbf55a688e969c
COCOAPODS: 1.12.1

View File

@@ -67,13 +67,16 @@
"renameBreakoutRoom": "Breakout-Raum umbenennen",
"sendToBreakoutRoom": "Anwesende in Breakout-Raum verschieben:"
},
"breakoutList": "Breakout-Liste",
"defaultName": "Breakout-Raum #{{index}}",
"hideParticipantList": "Teilnehmerliste ausblenden",
"mainRoom": "Hauptraum",
"notifications": {
"joined": "Breakout-Raum \"{{name}}\" betreten",
"joinedMainRoom": "Hauptraum betreten",
"joinedTitle": "Breakout-Räume"
}
},
"showParticipantList": "Teilnehmerliste anzeigen"
},
"calendarSync": {
"addMeetingURL": "Konferenzlink hinzufügen",

View File

@@ -67,13 +67,16 @@
"renameBreakoutRoom": "Rename breakout room",
"sendToBreakoutRoom": "Send participant to:"
},
"breakoutList": "breakout list",
"defaultName": "Breakout room #{{index}}",
"hideParticipantList": "Hide participant list",
"mainRoom": "Main room",
"notifications": {
"joined": "Joining the \"{{name}}\" breakout room",
"joinedMainRoom": "Joining the main room",
"joinedTitle": "Breakout Rooms"
}
},
"showParticipantList": "Show participant list"
},
"calendarSync": {
"addMeetingURL": "Add a meeting link",

View File

@@ -0,0 +1,10 @@
Jitsi Meet lets you stay in touch with all your teams, be they family, friends, or colleagues. Instant video conferences, efficiently adapting to your scale.
* Unlimited users: There are no artificial restrictions on the number of users or conference participants. Server power and bandwidth are the only limiting factors.
* No account needed.
* Lock-protected rooms: Control the access to your conferences with a password.
* Encrypted by default.
* High quality: Audio and video are delivered with the clarity and richness of Opus and VP8.
* Web browser ready: No downloads are required of your friends to join the conversation. Jitsi Meet works directly within their browsers as well. Simply share your conference URL with others to get started.
* 100% open source: Powered by awesome communities from all over the world. And your friends at 8x8.
* Invite by pretty URLs: You can meet at the easy to remember https://MySite.com/OurConf of your choice instead of joining the hard to remember rooms with seemingly random sequences of numbers and letters in their names.

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 302 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 168 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 243 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 928 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 534 KiB

View File

@@ -0,0 +1 @@
Secure, Simple and Scalable Video Conferences with state-of-the-art video quality

View File

@@ -403,7 +403,16 @@ export default class JitsiMeetExternalAPI extends EventEmitter {
const frameName = `jitsiConferenceFrame${id}`;
this._frame = document.createElement('iframe');
this._frame.allow = 'camera; microphone; display-capture; autoplay; clipboard-write; hid; screen-wake-lock';
this._frame.allow = [
'autoplay',
'camera',
'clipboard-write',
'compute-pressure',
'display-capture',
'hid',
'microphone',
'screen-wake-lock'
].join('; ');
this._frame.name = frameName;
this._frame.id = frameName;
this._setSize(height, width);

View File

@@ -5,6 +5,7 @@ import {
notifyMicError
} from '../../react/features/base/devices/actions.web';
import {
flattenAvailableDevices,
getAudioOutputDeviceId
} from '../../react/features/base/devices/functions.web';
import { updateSettings } from '../../react/features/base/settings/actions';
@@ -186,7 +187,7 @@ export default {
* @returns {boolean}
*/
newDeviceListAddedLabelsOnly(oldDevices, newDevices) {
const oldDevicesFlattend = oldDevices.audioInput.concat(oldDevices.audioOutput).concat(oldDevices.videoInput);
const oldDevicesFlattend = flattenAvailableDevices(oldDevices);
if (oldDevicesFlattend.length !== newDevices.length) {
return false;

157
package-lock.json generated
View File

@@ -17,7 +17,7 @@
"@giphy/react-components": "6.8.1",
"@giphy/react-native-sdk": "2.3.0",
"@jitsi/excalidraw": "https://github.com/jitsi/excalidraw/releases/download/v0.0.14/jitsi-excalidraw-0.0.14.tgz",
"@jitsi/js-utils": "2.1.3",
"@jitsi/js-utils": "2.2.0",
"@jitsi/logger": "2.0.2",
"@jitsi/rnnoise-wasm": "0.1.0",
"@jitsi/rtcstats": "9.5.1",
@@ -42,7 +42,7 @@
"@vladmandic/human-models": "2.5.9",
"@xmldom/xmldom": "0.8.7",
"amplitude-js": "8.2.1",
"base64-js": "1.3.1",
"base64-js": "1.5.1",
"bc-css-flags": "3.0.0",
"clipboard-copy": "4.0.1",
"clsx": "1.1.1",
@@ -59,7 +59,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/v1686.0.0+1b5830f1/lib-jitsi-meet.tgz",
"lib-jitsi-meet": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v1695.0.0+51c2187b/lib-jitsi-meet.tgz",
"lodash": "4.17.21",
"moment": "2.29.4",
"moment-duration-format": "2.2.2",
@@ -3174,13 +3174,13 @@
}
},
"node_modules/@jitsi/js-utils": {
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/@jitsi/js-utils/-/js-utils-2.1.3.tgz",
"integrity": "sha512-fXoNLu2JHQGPgzjCDG5qWPPStiXI+AxSzu1JIVY4dMULIFsw4a+doMgcjTIlSfB393J2xccKQYwZSejZUn9MEw==",
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/@jitsi/js-utils/-/js-utils-2.2.0.tgz",
"integrity": "sha512-C1dZikxS899o49nclZe9TeehmDykxj1bMwmjcJidzzkq7lI593frHJRntPwV43zeZH5X0QslqeBnzOJq8B0ZIA==",
"dependencies": {
"@hapi/bourne": "^3.0.0",
"bowser": "2.7.0",
"js-md5": "0.7.3"
"js-md5": "0.7.3",
"ua-parser-js": "1.0.35"
}
},
"node_modules/@jitsi/js-utils/node_modules/js-md5": {
@@ -7446,9 +7446,23 @@
}
},
"node_modules/base64-js": {
"version": "1.3.1",
"resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.3.1.tgz",
"integrity": "sha512-mLQ4i2QO1ytvGWFWmcngKO//JXAQueZvwEKtjgQFM4jIK0kU+ytMfplL8j+n5mspOfjHwoAg+9yhb7BwAHm36g=="
"version": "1.5.1",
"resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz",
"integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/feross"
},
{
"type": "patreon",
"url": "https://www.patreon.com/feross"
},
{
"type": "consulting",
"url": "https://feross.org/support"
}
]
},
"node_modules/batch": {
"version": "0.6.1",
@@ -7556,11 +7570,6 @@
"resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz",
"integrity": "sha1-aN/1++YMUes3cl6p4+0xDcwed24="
},
"node_modules/bowser": {
"version": "2.7.0",
"resolved": "https://registry.npmjs.org/bowser/-/bowser-2.7.0.tgz",
"integrity": "sha512-aIlMvstvu8x+34KEiOHD3AsBgdrzg6sxALYiukOWhFvGMbQI6TRP/iY0LMhUrHs56aD6P1G0Z7h45PUJaa5m9w=="
},
"node_modules/brace-expansion": {
"version": "1.1.11",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
@@ -12841,12 +12850,12 @@
},
"node_modules/lib-jitsi-meet": {
"version": "0.0.0",
"resolved": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v1686.0.0+1b5830f1/lib-jitsi-meet.tgz",
"integrity": "sha512-HTZgFGNddQZ/Xv7AbUQg0o8OcxcdUa7i73uGzIffpLVhWMdVH7V1/K72hQULcwi9lmNwo/2odDfyfjizIhoy8w==",
"resolved": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v1695.0.0+51c2187b/lib-jitsi-meet.tgz",
"integrity": "sha512-bhEN+8cZ1zIZM67/xmH+mCIM2R0EMFt4GgF9f8PvOGKPOB7uArJ2rXVpwrNirG3jOEY/TYjF8IgYqlslxvv8Kg==",
"hasInstallScript": true,
"license": "Apache-2.0",
"dependencies": {
"@jitsi/js-utils": "2.1.3",
"@jitsi/js-utils": "2.2.0",
"@jitsi/logger": "2.0.2",
"@jitsi/rtcstats": "9.5.1",
"@jitsi/sdp-interop": "git+https://github.com/jitsi/sdp-interop#3d49eb4aa26863a3f8d32d7581cdb4321244266b",
@@ -12883,6 +12892,11 @@
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
}
},
"node_modules/lib-jitsi-meet/node_modules/base64-js": {
"version": "1.3.1",
"resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.3.1.tgz",
"integrity": "sha512-mLQ4i2QO1ytvGWFWmcngKO//JXAQueZvwEKtjgQFM4jIK0kU+ytMfplL8j+n5mspOfjHwoAg+9yhb7BwAHm36g=="
},
"node_modules/lib-jitsi-meet/node_modules/chalk": {
"version": "4.1.2",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
@@ -15133,25 +15147,6 @@
"node": ">=6"
}
},
"node_modules/plist/node_modules/base64-js": {
"version": "1.5.1",
"resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz",
"integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/feross"
},
{
"type": "patreon",
"url": "https://www.patreon.com/feross"
},
{
"type": "consulting",
"url": "https://feross.org/support"
}
]
},
"node_modules/portfinder": {
"version": "1.0.28",
"resolved": "https://registry.npmjs.org/portfinder/-/portfinder-1.0.28.tgz",
@@ -16617,25 +16612,6 @@
"react-native": ">=0.60.0"
}
},
"node_modules/react-native-webrtc/node_modules/base64-js": {
"version": "1.5.1",
"resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz",
"integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/feross"
},
{
"type": "patreon",
"url": "https://www.patreon.com/feross"
},
{
"type": "consulting",
"url": "https://feross.org/support"
}
]
},
"node_modules/react-native-webrtc/node_modules/event-target-shim": {
"version": "6.0.2",
"resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-6.0.2.tgz",
@@ -19088,6 +19064,24 @@
"node": ">=12.20"
}
},
"node_modules/ua-parser-js": {
"version": "1.0.35",
"resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-1.0.35.tgz",
"integrity": "sha512-fKnGuqmTBnIE+/KXSzCn4db8RTigUzw1AN0DmdU6hJovUTbYJKyqj+8Mt1c4VfRDnOVJnENmfYkIPZ946UrSAA==",
"funding": [
{
"type": "opencollective",
"url": "https://opencollective.com/ua-parser-js"
},
{
"type": "paypal",
"url": "https://paypal.me/faisalman"
}
],
"engines": {
"node": "*"
}
},
"node_modules/uc.micro": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-1.0.6.tgz",
@@ -22656,13 +22650,13 @@
"integrity": "sha512-iK7p7i6qJFOkjTVZhWDvurDW1u+eMoOhAVgpab9CZEqCTX+W4Ih4AOPrUpf+mjaAHK5XqmQZSc5nVEpMg+xIGQ=="
},
"@jitsi/js-utils": {
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/@jitsi/js-utils/-/js-utils-2.1.3.tgz",
"integrity": "sha512-fXoNLu2JHQGPgzjCDG5qWPPStiXI+AxSzu1JIVY4dMULIFsw4a+doMgcjTIlSfB393J2xccKQYwZSejZUn9MEw==",
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/@jitsi/js-utils/-/js-utils-2.2.0.tgz",
"integrity": "sha512-C1dZikxS899o49nclZe9TeehmDykxj1bMwmjcJidzzkq7lI593frHJRntPwV43zeZH5X0QslqeBnzOJq8B0ZIA==",
"requires": {
"@hapi/bourne": "^3.0.0",
"bowser": "2.7.0",
"js-md5": "0.7.3"
"js-md5": "0.7.3",
"ua-parser-js": "1.0.35"
},
"dependencies": {
"js-md5": {
@@ -25853,9 +25847,9 @@
"integrity": "sha1-eAqZyE59YAJgNhURxId2E78k9rs="
},
"base64-js": {
"version": "1.3.1",
"resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.3.1.tgz",
"integrity": "sha512-mLQ4i2QO1ytvGWFWmcngKO//JXAQueZvwEKtjgQFM4jIK0kU+ytMfplL8j+n5mspOfjHwoAg+9yhb7BwAHm36g=="
"version": "1.5.1",
"resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz",
"integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA=="
},
"batch": {
"version": "0.6.1",
@@ -25953,11 +25947,6 @@
"resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz",
"integrity": "sha1-aN/1++YMUes3cl6p4+0xDcwed24="
},
"bowser": {
"version": "2.7.0",
"resolved": "https://registry.npmjs.org/bowser/-/bowser-2.7.0.tgz",
"integrity": "sha512-aIlMvstvu8x+34KEiOHD3AsBgdrzg6sxALYiukOWhFvGMbQI6TRP/iY0LMhUrHs56aD6P1G0Z7h45PUJaa5m9w=="
},
"brace-expansion": {
"version": "1.1.11",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
@@ -29921,10 +29910,10 @@
}
},
"lib-jitsi-meet": {
"version": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v1686.0.0+1b5830f1/lib-jitsi-meet.tgz",
"integrity": "sha512-HTZgFGNddQZ/Xv7AbUQg0o8OcxcdUa7i73uGzIffpLVhWMdVH7V1/K72hQULcwi9lmNwo/2odDfyfjizIhoy8w==",
"version": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v1695.0.0+51c2187b/lib-jitsi-meet.tgz",
"integrity": "sha512-bhEN+8cZ1zIZM67/xmH+mCIM2R0EMFt4GgF9f8PvOGKPOB7uArJ2rXVpwrNirG3jOEY/TYjF8IgYqlslxvv8Kg==",
"requires": {
"@jitsi/js-utils": "2.1.3",
"@jitsi/js-utils": "2.2.0",
"@jitsi/logger": "2.0.2",
"@jitsi/rtcstats": "9.5.1",
"@jitsi/sdp-interop": "git+https://github.com/jitsi/sdp-interop#3d49eb4aa26863a3f8d32d7581cdb4321244266b",
@@ -29955,6 +29944,11 @@
"color-convert": "^2.0.1"
}
},
"base64-js": {
"version": "1.3.1",
"resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.3.1.tgz",
"integrity": "sha512-mLQ4i2QO1ytvGWFWmcngKO//JXAQueZvwEKtjgQFM4jIK0kU+ytMfplL8j+n5mspOfjHwoAg+9yhb7BwAHm36g=="
},
"chalk": {
"version": "4.1.2",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
@@ -31667,13 +31661,6 @@
"requires": {
"base64-js": "^1.5.1",
"xmlbuilder": "^15.1.1"
},
"dependencies": {
"base64-js": {
"version": "1.5.1",
"resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz",
"integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA=="
}
}
},
"portfinder": {
@@ -32688,11 +32675,6 @@
"event-target-shim": "6.0.2"
},
"dependencies": {
"base64-js": {
"version": "1.5.1",
"resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz",
"integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA=="
},
"event-target-shim": {
"version": "6.0.2",
"resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-6.0.2.tgz",
@@ -34527,6 +34509,11 @@
"integrity": "sha512-cW9T5W9xY37cc+jfEnaUvX91foxtHkza3Nw3wkoF4sSlKn0MONdkdEndig/qPBWXNkmplh3NzayQzCiHM4/hqw==",
"dev": true
},
"ua-parser-js": {
"version": "1.0.35",
"resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-1.0.35.tgz",
"integrity": "sha512-fKnGuqmTBnIE+/KXSzCn4db8RTigUzw1AN0DmdU6hJovUTbYJKyqj+8Mt1c4VfRDnOVJnENmfYkIPZ946UrSAA=="
},
"uc.micro": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-1.0.6.tgz",

View File

@@ -23,7 +23,7 @@
"@giphy/react-components": "6.8.1",
"@giphy/react-native-sdk": "2.3.0",
"@jitsi/excalidraw": "https://github.com/jitsi/excalidraw/releases/download/v0.0.14/jitsi-excalidraw-0.0.14.tgz",
"@jitsi/js-utils": "2.1.3",
"@jitsi/js-utils": "2.2.0",
"@jitsi/logger": "2.0.2",
"@jitsi/rnnoise-wasm": "0.1.0",
"@jitsi/rtcstats": "9.5.1",
@@ -48,7 +48,7 @@
"@vladmandic/human-models": "2.5.9",
"@xmldom/xmldom": "0.8.7",
"amplitude-js": "8.2.1",
"base64-js": "1.3.1",
"base64-js": "1.5.1",
"bc-css-flags": "3.0.0",
"clipboard-copy": "4.0.1",
"clsx": "1.1.1",
@@ -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/v1686.0.0+1b5830f1/lib-jitsi-meet.tgz",
"lib-jitsi-meet": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v1695.0.0+51c2187b/lib-jitsi-meet.tgz",
"lodash": "4.17.21",
"moment": "2.29.4",
"moment-duration-format": "2.2.2",

View File

@@ -23,10 +23,11 @@
"grapheme-splitter": "1.0.4",
"i18n-iso-countries": "6.8.0",
"i18next": "17.0.6",
"i18next-http-backend": "^2.2.1",
"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/v1681.0.0+6cd397fa/lib-jitsi-meet.tgz",
"lib-jitsi-meet": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v1687.0.0+cafe30d7/lib-jitsi-meet.tgz",
"lodash": "4.17.21",
"moment": "2.29.4",
"moment-duration-format": "2.2.2",
@@ -1275,6 +1276,14 @@
}
}
},
"node_modules/cross-fetch": {
"version": "3.1.6",
"resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.1.6.tgz",
"integrity": "sha512-riRvo06crlE8HiqOwIpQhxwdOk4fOeR7FVM/wXoxchFEqMNUjvbs3bfo4OTgMEMHzppd4DxFBDbyySj8Cv781g==",
"dependencies": {
"node-fetch": "^2.6.11"
}
},
"node_modules/cross-spawn": {
"version": "6.0.5",
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz",
@@ -1962,6 +1971,14 @@
"@babel/runtime": "^7.3.1"
}
},
"node_modules/i18next-http-backend": {
"version": "2.2.2",
"resolved": "https://registry.npmjs.org/i18next-http-backend/-/i18next-http-backend-2.2.2.tgz",
"integrity": "sha512-mJu4ZqzDtBiU3O4GV9AbK5ekEqoDMdMnCl3pkdXmb5b8yoIH//u8FsmIe6C5qXb3teZu+j6VMi20tjUgzeABiw==",
"dependencies": {
"cross-fetch": "3.1.6"
}
},
"node_modules/ieee754": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz",
@@ -2464,13 +2481,14 @@
},
"node_modules/lib-jitsi-meet": {
"version": "0.0.0",
"resolved": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v1681.0.0+6cd397fa/lib-jitsi-meet.tgz",
"integrity": "sha512-IUjwOod8/HX785klIwINfmnxjrE4iwaMZCEmng9SQoCdAtZDqo8XsAI0+pLyBpttJ/fhUkBI/ox8Vw2Ic4TB9w==",
"resolved": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v1687.0.0+cafe30d7/lib-jitsi-meet.tgz",
"integrity": "sha512-ky3q5gLeLDHFtenXe7+QlxAh33opz0a3bqEp8svA9urR1AICUi0QthOwmUerTdfSRFU3wfVObF9axlX1fbHZ3A==",
"hasInstallScript": true,
"license": "Apache-2.0",
"dependencies": {
"@jitsi/js-utils": "2.1.3",
"@jitsi/logger": "2.0.2",
"@jitsi/rtcstats": "9.5.1",
"@jitsi/sdp-interop": "git+https://github.com/jitsi/sdp-interop#3d49eb4aa26863a3f8d32d7581cdb4321244266b",
"@jitsi/sdp-simulcast": "0.4.0",
"@testrtc/watchrtc-sdk": "1.36.3",
@@ -2654,6 +2672,25 @@
"tslib": "^2.0.3"
}
},
"node_modules/node-fetch": {
"version": "2.7.0",
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz",
"integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==",
"dependencies": {
"whatwg-url": "^5.0.0"
},
"engines": {
"node": "4.x || >=6.0.0"
},
"peerDependencies": {
"encoding": "^0.1.0"
},
"peerDependenciesMeta": {
"encoding": {
"optional": true
}
}
},
"node_modules/node-releases": {
"version": "2.0.13",
"resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.13.tgz",
@@ -3510,6 +3547,11 @@
"node": ">=8.0"
}
},
"node_modules/tr46": {
"version": "0.0.3",
"resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz",
"integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw=="
},
"node_modules/tslib": {
"version": "2.6.2",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz",
@@ -3653,6 +3695,15 @@
"npm": ">=3.10.0"
}
},
"node_modules/whatwg-url": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz",
"integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==",
"dependencies": {
"tr46": "~0.0.3",
"webidl-conversions": "^3.0.0"
}
},
"node_modules/whatwg-url-without-unicode": {
"version": "8.0.0-3",
"resolved": "https://registry.npmjs.org/whatwg-url-without-unicode/-/whatwg-url-without-unicode-8.0.0-3.tgz",
@@ -3666,6 +3717,11 @@
"node": ">=10"
}
},
"node_modules/whatwg-url/node_modules/webidl-conversions": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
"integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ=="
},
"node_modules/which": {
"version": "1.3.1",
"resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz",
@@ -4568,6 +4624,14 @@
"path-type": "^4.0.0"
}
},
"cross-fetch": {
"version": "3.1.6",
"resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.1.6.tgz",
"integrity": "sha512-riRvo06crlE8HiqOwIpQhxwdOk4fOeR7FVM/wXoxchFEqMNUjvbs3bfo4OTgMEMHzppd4DxFBDbyySj8Cv781g==",
"requires": {
"node-fetch": "^2.6.11"
}
},
"cross-spawn": {
"version": "6.0.5",
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz",
@@ -5069,6 +5133,14 @@
"@babel/runtime": "^7.3.1"
}
},
"i18next-http-backend": {
"version": "2.2.2",
"resolved": "https://registry.npmjs.org/i18next-http-backend/-/i18next-http-backend-2.2.2.tgz",
"integrity": "sha512-mJu4ZqzDtBiU3O4GV9AbK5ekEqoDMdMnCl3pkdXmb5b8yoIH//u8FsmIe6C5qXb3teZu+j6VMi20tjUgzeABiw==",
"requires": {
"cross-fetch": "3.1.6"
}
},
"ieee754": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz",
@@ -5404,11 +5476,12 @@
}
},
"lib-jitsi-meet": {
"version": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v1681.0.0+6cd397fa/lib-jitsi-meet.tgz",
"integrity": "sha512-IUjwOod8/HX785klIwINfmnxjrE4iwaMZCEmng9SQoCdAtZDqo8XsAI0+pLyBpttJ/fhUkBI/ox8Vw2Ic4TB9w==",
"version": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v1687.0.0+cafe30d7/lib-jitsi-meet.tgz",
"integrity": "sha512-ky3q5gLeLDHFtenXe7+QlxAh33opz0a3bqEp8svA9urR1AICUi0QthOwmUerTdfSRFU3wfVObF9axlX1fbHZ3A==",
"requires": {
"@jitsi/js-utils": "2.1.3",
"@jitsi/logger": "2.0.2",
"@jitsi/rtcstats": "9.5.1",
"@jitsi/sdp-interop": "git+https://github.com/jitsi/sdp-interop#3d49eb4aa26863a3f8d32d7581cdb4321244266b",
"@jitsi/sdp-simulcast": "0.4.0",
"@testrtc/watchrtc-sdk": "1.36.3",
@@ -5564,6 +5637,14 @@
"tslib": "^2.0.3"
}
},
"node-fetch": {
"version": "2.7.0",
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz",
"integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==",
"requires": {
"whatwg-url": "^5.0.0"
}
},
"node-releases": {
"version": "2.0.13",
"resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.13.tgz",
@@ -6162,6 +6243,11 @@
"is-number": "^7.0.0"
}
},
"tr46": {
"version": "0.0.3",
"resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz",
"integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw=="
},
"tslib": {
"version": "2.6.2",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz",
@@ -6257,6 +6343,22 @@
"sdp": "^3.0.2"
}
},
"whatwg-url": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz",
"integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==",
"requires": {
"tr46": "~0.0.3",
"webidl-conversions": "^3.0.0"
},
"dependencies": {
"webidl-conversions": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
"integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ=="
}
}
},
"whatwg-url-without-unicode": {
"version": "8.0.0-3",
"resolved": "https://registry.npmjs.org/whatwg-url-without-unicode/-/whatwg-url-without-unicode-8.0.0-3.tgz",

View File

@@ -25,9 +25,10 @@
"i18n-iso-countries": "6.8.0",
"i18next": "17.0.6",
"js-md5": "0.6.1",
"i18next-http-backend": "^2.2.1",
"js-sha512": "0.8.0",
"jwt-decode": "2.2.0",
"lib-jitsi-meet": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v1681.0.0+6cd397fa/lib-jitsi-meet.tgz",
"lib-jitsi-meet": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v1687.0.0+cafe30d7/lib-jitsi-meet.tgz",
"lodash": "4.17.21",
"moment": "2.29.4",
"moment-duration-format": "2.2.2",

View File

@@ -25,6 +25,17 @@ function updateDependencies() {
updated = true;
}
if (!semver.valid(packageJSON.dependencies[key])) {
packageJSON.dependencies[key] = RNSDKpackageJSON.peerDependencies[key];
updated = true;
console.log(`
We changed ${key} version number from ${packageJSON.dependencies[key]} to ${RNSDKpackageJSON.peerDependencies[key]}`
);
continue;
}
if (semver.satisfies(RNSDKpackageJSON.peerDependencies[key], `=${packageJSON.dependencies[key]}`)) {
continue;
}

View File

@@ -224,7 +224,7 @@ export function initAnalytics(store: IStore, handlers: Array<Object>) {
// Set the handlers last, since this triggers emptying of the cache
analytics.setAnalyticsHandlers(handlers);
if (!isMobileBrowser() && browser.isChrome()) {
if (!isMobileBrowser() && browser.isChromiumBased()) {
const bannerCfg = state['features/base/config'].chromeExtensionBanner;
checkChromeExtensionsInstalled(bannerCfg).then(extensionsInstalled => {

View File

@@ -1,6 +1,14 @@
import { getLocationContextRoot } from '../base/util/uri';
// @ts-ignore
// eslint-disable-next-line
import { openTokenAuthUrl } from '../authentication/actions';
// @ts-ignore
import { getTokenAuthUrl, isTokenAuthEnabled } from '../authentication/functions';
import { getJwtExpirationDate } from '../base/jwt/functions';
import { getLocationContextRoot, parseURIString } from '../base/util/uri';
import { addTrackStateToURL } from './functions.any';
import logger from './logger';
import { IStore } from './types';
/**
@@ -83,4 +91,53 @@ export function reloadWithStoredParams() {
};
}
/**
* Checks whether tokenAuthUrl is set, we have a jwt token that will expire soon
* and redirect to the auth url to obtain new token if this is the case.
*
* @param {Dispatch} dispatch - The Redux dispatch function.
* @param {Function} getState - The Redux state.
* @param {Function} failureCallback - The callback on failure to obtain auth url.
* @returns {boolean} Whether we will redirect or not.
*/
export function maybeRedirectToTokenAuthUrl(
dispatch: IStore['dispatch'], getState: IStore['getState'], failureCallback: Function) {
const state = getState();
const config = state['features/base/config'];
const { locationURL = { href: '' } } = state['features/base/connection'];
if (!isTokenAuthEnabled(config)) {
return false;
}
// if tokenAuthUrl check jwt if is about to expire go through the url to get new token
const jwt = state['features/base/jwt'].jwt;
const expirationDate = getJwtExpirationDate(jwt);
// if there is jwt and its expiration time is less than 3 minutes away
// let's obtain new token
if (expirationDate && expirationDate.getTime() - Date.now() < 3 * 60 * 1000) {
const room = state['features/base/conference'].room;
const { tenant } = parseURIString(locationURL.href) || {};
getTokenAuthUrl(config, room, tenant, true)
.then((tokenAuthServiceUrl: string | undefined) => {
if (!tokenAuthServiceUrl) {
logger.warn('Cannot handle login, token service URL is not set');
return Promise.reject();
}
return dispatch(openTokenAuthUrl(tokenAuthServiceUrl));
})
.catch(() => {
failureCallback();
});
return true;
}
return false;
}

View File

@@ -1,4 +1,5 @@
import { setRoom } from '../base/conference/actions';
import { getConferenceState } from '../base/conference/functions';
import {
configWillLoad,
loadConfigError,
@@ -10,6 +11,7 @@ import {
restoreConfig
} from '../base/config/functions.native';
import { connect, disconnect, setLocationURL } from '../base/connection/actions.native';
import { JITSI_CONNECTION_URL_KEY } from '../base/connection/constants';
import { loadConfig } from '../base/lib-jitsi-meet/functions.native';
import { createDesiredLocalTracks } from '../base/tracks/actions.native';
import isInsecureRoomName from '../base/util/isInsecureRoomName';
@@ -29,6 +31,7 @@ import { screen } from '../mobile/navigation/routes';
import { clearNotifications } from '../notifications/actions';
import { isUnsafeRoomWarningEnabled } from '../prejoin/functions';
import { maybeRedirectToTokenAuthUrl } from './actions.any';
import { addTrackStateToURL, getDefaultURL } from './functions.native';
import logger from './logger';
import { IReloadNowOptions, IStore } from './types';
@@ -72,11 +75,25 @@ export function appNavigate(uri?: string, options: IReloadNowOptions = {}) {
}
location.protocol || (location.protocol = 'https:');
const { contextRoot, host, room } = location;
const { contextRoot, host, hostname, pathname, room } = location;
const locationURL = new URL(location.toString());
const { conference } = getConferenceState(getState());
if (room) {
navigateRoot(screen.connecting);
if (conference) {
// We need to check if the location is the same with the previous one.
const currentLocationURL = conference?.getConnection()[JITSI_CONNECTION_URL_KEY];
const { hostname: currentHostName, pathname: currentPathName } = currentLocationURL;
if (currentHostName === hostname && currentPathName === pathname) {
logger.warn(`Joining same conference using URL: ${currentLocationURL}`);
return;
}
} else {
navigateRoot(screen.connecting);
}
}
dispatch(disconnect());
@@ -188,10 +205,18 @@ export function reloadNow() {
// @ts-ignore
const newURL = addTrackStateToURL(locationURL, state);
logger.info(`Reloading the conference using URL: ${locationURL}`);
const reloadAction = () => {
logger.info(`Reloading the conference using URL: ${locationURL}`);
dispatch(appNavigate(toURLString(newURL), {
hidePrejoin: true
}));
dispatch(appNavigate(toURLString(newURL), {
hidePrejoin: true
}));
};
if (maybeRedirectToTokenAuthUrl(dispatch, getState, reloadAction)) {
return;
}
reloadAction();
};
}

View File

@@ -15,6 +15,7 @@ import { NOTIFICATION_TIMEOUT_TYPE } from '../notifications/constants';
import { isWelcomePageEnabled } from '../welcome/functions';
import {
maybeRedirectToTokenAuthUrl,
redirectToStaticPage,
redirectWithStoredParams,
reloadWithStoredParams
@@ -170,8 +171,16 @@ export function reloadNow() {
const state = getState();
const { locationURL } = state['features/base/connection'];
logger.info(`Reloading the conference using URL: ${locationURL}`);
const reloadAction = () => {
logger.info(`Reloading the conference using URL: ${locationURL}`);
dispatch(reloadWithStoredParams());
dispatch(reloadWithStoredParams());
};
if (maybeRedirectToTokenAuthUrl(dispatch, getState, reloadAction)) {
return;
}
reloadAction();
};
}

View File

@@ -112,13 +112,21 @@ export class App extends AbstractApp<IProps> {
async _extraInit() {
const { dispatch, getState } = this.state.store ?? {};
const { flags = {} } = this.props;
let callIntegrationEnabled = flags[CALL_INTEGRATION_ENABLED as keyof typeof flags];
// CallKit does not work on the simulator, make sure we disable it.
if (Platform.OS === 'ios' && DeviceInfo.isEmulatorSync()) {
flags['call-integration.enabled'] = false;
flags[CALL_INTEGRATION_ENABLED] = false;
callIntegrationEnabled = false;
logger.info('Disabling CallKit because this is a simulator');
}
// Disable Android ConnectionService by default.
if (Platform.OS === 'android' && typeof callIntegrationEnabled === 'undefined') {
flags[CALL_INTEGRATION_ENABLED] = false;
callIntegrationEnabled = false;
}
// We set these early enough so then we avoid any unnecessary re-renders.
dispatch?.(updateFlags(flags));
@@ -163,8 +171,6 @@ export class App extends AbstractApp<IProps> {
dispatch?.(updateSettings(this.props.userInfo || {}));
// Update settings with feature-flag.
const callIntegrationEnabled = flags[CALL_INTEGRATION_ENABLED as keyof typeof flags];
if (typeof callIntegrationEnabled !== 'undefined') {
dispatch?.(updateSettings({ disableCallIntegration: !callIntegrationEnabled }));
}

View File

@@ -1,12 +1,13 @@
// @ts-expect-error
import { generateRoomWithoutSeparator } from '@jitsi/js-utils/random';
import { getTokenAuthUrl } from '../authentication/functions';
import { getTokenAuthUrl } from '../authentication/functions.web';
import { IStateful } from '../base/app/types';
import { isRoomValid } from '../base/conference/functions';
import { isSupportedBrowser } from '../base/environment/environment';
import { browser } from '../base/lib-jitsi-meet';
import { toState } from '../base/redux/functions';
import { parseURIString } from '../base/util/uri';
import Conference from '../conference/components/web/Conference';
import { getDeepLinkingPage } from '../deep-linking/functions';
import UnsupportedDesktopBrowser from '../unsupported-browser/components/UnsupportedDesktopBrowser';
@@ -52,9 +53,16 @@ function _getWebConferenceRoute(state: IReduxState) {
if (!browser.isElectron() && config.tokenAuthUrl && config.tokenAuthUrlAutoRedirect
&& state['features/authentication'].tokenAuthUrlSuccessful
&& !state['features/base/jwt'].jwt && room) {
route.href = getTokenAuthUrl(config, room);
const { locationURL = { href: '' } } = state['features/base/connection'];
const { tenant } = parseURIString(locationURL.href) || {};
return Promise.resolve(route);
return getTokenAuthUrl(config, room, tenant)
.then((url: string | undefined) => {
route.href = url;
return route;
})
.catch(() => Promise.resolve(route));
}
// Update the location if it doesn't match. This happens when a room is

View File

@@ -1,11 +1,10 @@
import { Linking, Platform } from 'react-native';
import { Linking } from 'react-native';
import { appNavigate } from '../app/actions.native';
import { IStore } from '../app/types';
import { conferenceLeft } from '../base/conference/actions';
import { connectionFailed } from '../base/connection/actions.native';
import { set } from '../base/redux/functions';
import { appendURLHashParam } from '../base/util/uri';
import { CANCEL_LOGIN } from './actionTypes';
import { stopWaitForOwner } from './actions.any';
@@ -85,12 +84,7 @@ export function redirectToDefaultLocation() {
* @returns {Function}
*/
export function openTokenAuthUrl(tokenAuthServiceUrl: string) {
let url = appendURLHashParam(tokenAuthServiceUrl, 'skipPrejoin', 'true');
// Append ios=true or android=true to the token URL.
url = appendURLHashParam(url, Platform.OS, 'true');
return () => {
Linking.openURL(url);
Linking.openURL(tokenAuthServiceUrl);
};
}

View File

@@ -2,7 +2,6 @@ import { maybeRedirectToWelcomePage } from '../app/actions.web';
import { IStore } from '../app/types';
import { openDialog } from '../base/dialog/actions';
import { browser } from '../base/lib-jitsi-meet';
import { appendURLHashParam } from '../base/util/uri';
import { CANCEL_LOGIN } from './actionTypes';
import LoginQuestionDialog from './components/web/LoginQuestionDialog';
@@ -57,14 +56,10 @@ export function redirectToDefaultLocation() {
export function openTokenAuthUrl(tokenAuthServiceUrl: string): any {
return (dispatch: IStore['dispatch'], getState: IStore['getState']) => {
const redirect = () => {
// We have already shown the prejoin screen, no need to show it again after obtaining the token.
let url = appendURLHashParam(tokenAuthServiceUrl, 'skipPrejoin', 'true');
if (browser.isElectron()) {
url = appendURLHashParam(url, 'electron', 'true');
window.open(url, '_blank');
window.open(tokenAuthServiceUrl, '_blank');
} else {
window.location.href = url;
window.location.href = tokenAuthServiceUrl;
}
};

View File

@@ -0,0 +1,48 @@
import { IConfig } from '../base/config/configType';
import { getBackendSafeRoomName } from '../base/util/uri';
/**
* Checks if the token for authentication is available.
*
* @param {Object} config - Configuration state object from store.
* @returns {boolean}
*/
export const isTokenAuthEnabled = (config: IConfig): boolean =>
typeof config.tokenAuthUrl === 'string' && config.tokenAuthUrl.length > 0;
/**
* Returns the state that we can add as a parameter to the tokenAuthUrl.
*
* @param {string?} roomName - The room name.
* @param {string?} tenant - The tenant name if any.
* @param {boolean} skipPrejoin - Whether to skip pre-join page.
* @returns {Object} The state object.
*/
export const _getTokenAuthState = (
roomName: string | undefined,
tenant: string | undefined,
skipPrejoin: boolean | undefined = false): object => {
const state = {
room: roomName,
roomSafe: getBackendSafeRoomName(roomName),
tenant
};
if (skipPrejoin) {
// We have already shown the prejoin screen, no need to show it again after obtaining the token.
// @ts-ignore
state['config.prejoinConfig.enabled'] = false;
}
const params = new URLSearchParams(window.location.search);
for (const [ key, value ] of params) {
// we allow only config and interfaceConfig overrides in the state
if (key.startsWith('config.') || key.startsWith('interfaceConfig.')) {
// @ts-ignore
state[key] = value;
}
}
return state;
};

View File

@@ -0,0 +1,48 @@
import { Platform } from 'react-native';
import { IConfig } from '../base/config/configType';
import { _getTokenAuthState } from './functions.any';
export * from './functions.any';
/**
* Creates the URL pointing to JWT token authentication service. It is
* formatted from the 'urlPattern' argument which can contain the following
* constants:
* '{room}' - name of the conference room passed as <tt>roomName</tt>
* argument to this method.
*
* @param {Object} config - Configuration state object from store. A URL pattern pointing to the login service.
* @param {string} roomName - The name of the conference room for which the user will be authenticated.
* @param {string} tenant - The name of the conference tenant.
* @param {string} skipPrejoin - The name of the conference room for which the user will be authenticated.
*
* @returns {Promise<string|undefined>} - The URL pointing to JWT login service or
* <tt>undefined</tt> if the pattern stored in config is not a string and the URL can not be
* constructed.
*/
export const getTokenAuthUrl = (
config: IConfig,
roomName: string | undefined,
tenant: string | undefined,
skipPrejoin: boolean | undefined = false): Promise<string | undefined> => {
let url = config.tokenAuthUrl;
if (!url || !roomName) {
return Promise.resolve(undefined);
}
if (url.indexOf('{state}')) {
const state = _getTokenAuthState(roomName, tenant, skipPrejoin);
// Append ios=true or android=true to the token URL.
// @ts-ignore
state[Platform.OS] = true;
url = url.replace('{state}', encodeURIComponent(JSON.stringify(state)));
}
return Promise.resolve(url.replace('{room}', roomName));
};

View File

@@ -1,39 +0,0 @@
import { IConfig } from '../base/config/configType';
/**
* Checks if the token for authentication is available.
*
* @param {Object} config - Configuration state object from store.
* @returns {boolean}
*/
export const isTokenAuthEnabled = (config: IConfig) =>
typeof config.tokenAuthUrl === 'string'
&& config.tokenAuthUrl.length;
/**
* Creates the URL pointing to JWT token authentication service. It is
* formatted from the 'urlPattern' argument which can contain the following
* constants:
* '{room}' - name of the conference room passed as <tt>roomName</tt>
* argument to this method.
* '{roleUpgrade}' - will contain 'true' if the URL will be used for
* the role upgrade scenario, where user connects from anonymous domain and
* then gets upgraded to the moderator by logging-in from the popup window.
*
* @param {Object} config - Configuration state object from store. A URL pattern pointing to the login service.
* @param {string} roomName - The name of the conference room for which the user will be authenticated.
*
* @returns {string|undefined} - The URL pointing to JWT login service or
* <tt>undefined</tt> if the pattern stored in config is not a string and the URL can not be
* constructed.
*/
export const getTokenAuthUrl = (config: IConfig, roomName: string | undefined) => {
const url = config.tokenAuthUrl;
if (typeof url !== 'string' || !roomName) {
return undefined;
}
return url.replace('{room}', roomName);
};

View File

@@ -0,0 +1,92 @@
import base64js from 'base64-js';
import { IConfig } from '../base/config/configType';
import { browser } from '../base/lib-jitsi-meet';
import { _getTokenAuthState } from './functions.any';
export * from './functions.any';
/**
* Based on rfc7636 we need a random string for a code verifier.
*/
const POSSIBLE_CHARS = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
/**
* Crypto random, alternative of Math.random.
*
* @returns {float} A random value.
*/
function _cryptoRandom() {
const typedArray = new Uint8Array(1);
const randomValue = crypto.getRandomValues(typedArray)[0];
return randomValue / Math.pow(2, 8);
}
/**
* Creates the URL pointing to JWT token authentication service. It is
* formatted from the 'urlPattern' argument which can contain the following
* constants:
* '{room}' - name of the conference room passed as <tt>roomName</tt>
* argument to this method.
*
* @param {Object} config - Configuration state object from store. A URL pattern pointing to the login service.
* @param {string} roomName - The name of the conference room for which the user will be authenticated.
* @param {string} tenant - The name of the conference tenant.
* @param {string} skipPrejoin - The name of the conference room for which the user will be authenticated.
*
* @returns {Promise<string|undefined>} - The URL pointing to JWT login service or
* <tt>undefined</tt> if the pattern stored in config is not a string and the URL can not be
* constructed.
*/
export const getTokenAuthUrl = (
config: IConfig,
roomName: string | undefined,
tenant: string | undefined,
skipPrejoin: boolean | undefined = false): Promise<string | undefined> => {
let url = config.tokenAuthUrl;
if (!url || !roomName) {
return Promise.resolve(undefined);
}
if (url.indexOf('{state}')) {
const state = _getTokenAuthState(roomName, tenant, skipPrejoin);
if (browser.isElectron()) {
// @ts-ignore
state.electron = true;
}
url = url.replace('{state}', encodeURIComponent(JSON.stringify(state)));
}
url = url.replace('{room}', roomName);
if (url.indexOf('{code_challenge}')) {
let codeVerifier = '';
// random string
for (let i = 0; i < 64; i++) {
codeVerifier += POSSIBLE_CHARS.charAt(Math.floor(_cryptoRandom() * POSSIBLE_CHARS.length));
}
window.sessionStorage.setItem('code_verifier', codeVerifier);
return window.crypto.subtle.digest('SHA-256', new TextEncoder().encode(codeVerifier))
.then(digest => {
// prepare code challenge - base64 encoding without padding as described in:
// https://datatracker.ietf.org/doc/html/rfc7636#appendix-A
const codeChallenge = base64js.fromByteArray(new Uint8Array(digest))
.replace(/=/g, '')
.replace(/\+/g, '-')
.replace(/\//g, '_');
return url ? url.replace('{code_challenge}', codeChallenge) : undefined;
});
}
return Promise.resolve(url);
};

View File

@@ -14,7 +14,7 @@ import {
JitsiConnectionErrors
} from '../base/lib-jitsi-meet';
import MiddlewareRegistry from '../base/redux/MiddlewareRegistry';
import { getBackendSafeRoomName } from '../base/util/uri';
import { parseURIString } from '../base/util/uri';
import { openLogoutDialog } from '../settings/actions';
import {
@@ -254,7 +254,9 @@ function _isWaitingForOwner({ getState }: IStore) {
function _handleLogin({ dispatch, getState }: IStore) {
const state = getState();
const config = state['features/base/config'];
const room = getBackendSafeRoomName(state['features/base/conference'].room);
const room = state['features/base/conference'].room;
const { locationURL = { href: '' } } = state['features/base/connection'];
const { tenant } = parseURIString(locationURL.href) || {};
if (!room) {
logger.warn('Cannot handle login, room is undefined!');
@@ -268,16 +270,16 @@ function _handleLogin({ dispatch, getState }: IStore) {
return;
}
// FIXME: This method will not preserve the other URL params that were originally passed.
const tokenAuthServiceUrl = getTokenAuthUrl(config, room);
getTokenAuthUrl(config, room, tenant, true)
.then((tokenAuthServiceUrl: string | undefined) => {
if (!tokenAuthServiceUrl) {
logger.warn('Cannot handle login, token service URL is not set');
if (!tokenAuthServiceUrl) {
logger.warn('Cannot handle login, token service URL is not set');
return;
}
return;
}
dispatch(openTokenAuthUrl(tokenAuthServiceUrl));
return dispatch(openTokenAuthUrl(tokenAuthServiceUrl));
});
}
/**

View File

@@ -59,6 +59,7 @@ export interface IJitsiConference {
enableLobby: Function;
end: Function;
getBreakoutRooms: Function;
getConnection: Function;
getLocalParticipantProperty: Function;
getLocalTracks: Function;
getMeetingUniqueId: Function;
@@ -156,11 +157,13 @@ export interface IConferenceState {
export interface IJitsiConferenceRoom {
locked: boolean;
moderator: {
logout: Function;
};
myroomjid: string;
roomjid: string;
xmpp: {
moderator: {
logout: Function;
};
};
}
interface IConferenceFailedError extends Error {

View File

@@ -16,9 +16,13 @@ import {
} from './actionTypes';
import {
areDeviceLabelsInitialized,
areDevicesDifferent,
filterIgnoredDevices,
flattenAvailableDevices,
getDeviceIdByLabel,
getDeviceLabelById,
getDevicesFromURL,
logDevices,
setAudioOutputDeviceId
} from './functions';
import logger from './logger';
@@ -137,15 +141,21 @@ export function configureInitialDevices() {
* @returns {Function}
*/
export function getAvailableDevices() {
return (dispatch: IStore['dispatch']) => new Promise(resolve => {
return (dispatch: IStore['dispatch'], getState: IStore['getState']) => new Promise(resolve => {
const { mediaDevices } = JitsiMeetJS;
if (mediaDevices.isDeviceListAvailable()
&& mediaDevices.isDeviceChangeAvailable()) {
mediaDevices.enumerateDevices((devices: MediaDeviceInfo[]) => {
dispatch(updateDeviceList(devices));
const { filteredDevices, ignoredDevices } = filterIgnoredDevices(devices);
const oldDevices = flattenAvailableDevices(getState()['features/base/devices'].availableDevices);
resolve(devices);
if (areDevicesDifferent(oldDevices, filteredDevices)) {
logDevices(ignoredDevices, 'Ignored devices on device list changed:');
dispatch(updateDeviceList(filteredDevices));
}
resolve(filteredDevices);
});
} else {
resolve([]);

View File

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

View File

@@ -5,6 +5,7 @@ import { ISettingsState } from '../settings/reducer';
import { setNewAudioOutputDevice } from '../sounds/functions.web';
import { parseURLParams } from '../util/parseURLParams';
import { DEVICE_LABEL_PREFIXES_TO_IGNORE } from './constants';
import logger from './logger';
import { IDevicesState } from './types';
@@ -176,6 +177,74 @@ export function filterAudioDevices(devices: MediaDeviceInfo[]) {
return devices.filter(device => device.kind === 'audioinput');
}
/**
* Filters the devices that start with one of the prefixes from DEVICE_LABEL_PREFIXES_TO_IGNORE.
*
* @param {MediaDeviceInfo[]} devices - The devices to be filtered.
* @returns {MediaDeviceInfo[]} - The filtered devices.
*/
export function filterIgnoredDevices(devices: MediaDeviceInfo[] = []) {
const ignoredDevices: MediaDeviceInfo[] = [];
const filteredDevices = devices.filter(device => {
if (!device.label) {
return true;
}
if (DEVICE_LABEL_PREFIXES_TO_IGNORE.find(prefix => device.label?.startsWith(prefix))) {
ignoredDevices.push(device);
return false;
}
return true;
});
return {
filteredDevices,
ignoredDevices
};
}
/**
* Check if the passed device arrays are different.
*
* @param {MediaDeviceInfo[]} devices1 - Array with devices to be compared.
* @param {MediaDeviceInfo[]} devices2 - Array with devices to be compared.
* @returns {boolean} - True if the device arrays are different and false otherwise.
*/
export function areDevicesDifferent(devices1: MediaDeviceInfo[] = [], devices2: MediaDeviceInfo[] = []) {
if (devices1.length !== devices2.length) {
return true;
}
for (let i = 0; i < devices1.length; i++) {
const device1 = devices1[i];
const found = devices2.find(({ deviceId, groupId, kind, label }) =>
device1.deviceId === deviceId
&& device1.groupId === groupId
&& device1.kind === kind
&& device1.label === label
);
if (!found) {
return true;
}
}
return false;
}
/**
* Flattens the availableDevices from redux.
*
* @param {IDevicesState.availableDevices} devices - The available devices from redux.
* @returns {MediaDeviceInfo[]} - The flattened array of devices.
*/
export function flattenAvailableDevices(
{ audioInput = [], audioOutput = [], videoInput = [] }: IDevicesState['availableDevices']) {
return audioInput.concat(audioOutput).concat(videoInput);
}
/**
* We want to strip any device details that are not very user friendly, like usb ids put in brackets at the end.
*
@@ -240,6 +309,35 @@ export function getVideoDeviceIds(state: IReduxState) {
return state['features/base/devices'].availableDevices.videoInput?.map(({ deviceId }) => deviceId);
}
/**
* Converts an array of device info objects into string.
*
* @param {MediaDeviceInfo[]} devices - The devices.
* @returns {string}
*/
function devicesToStr(devices?: MediaDeviceInfo[]) {
return devices?.map(device => `\t\t${device.label}[${device.deviceId}]`).join('\n');
}
/**
* Logs an array of devices.
*
* @param {MediaDeviceInfo[]} devices - The array of devices.
* @param {string} title - The title that will be printed in the log.
* @returns {void}
*/
export function logDevices(devices: MediaDeviceInfo[], title = '') {
const deviceList = groupDevicesByKind(devices);
const audioInputs = devicesToStr(deviceList.audioInput);
const audioOutputs = devicesToStr(deviceList.audioOutput);
const videoInputs = devicesToStr(deviceList.videoInput);
logger.debug(`${title}:\n`
+ `audioInput:\n${audioInputs}\n`
+ `audioOutput:\n${audioOutputs}\n`
+ `videoInput:\n${videoInputs}`);
}
/**
* Set device id of the audio output device which is currently in use.
* Empty string stands for default device.

View File

@@ -33,11 +33,10 @@ import {
import {
areDeviceLabelsInitialized,
formatDeviceLabel,
groupDevicesByKind,
logDevices,
setAudioOutputDeviceId
} from './functions';
import logger from './logger';
import { IDevicesState } from './types';
const JITSI_TRACK_ERROR_TO_MESSAGE_KEY_MAP = {
microphone: {
@@ -62,25 +61,6 @@ const JITSI_TRACK_ERROR_TO_MESSAGE_KEY_MAP = {
*/
let permissionsListener: Function | undefined;
/**
* Logs the current device list.
*
* @param {Object} deviceList - Whatever is returned by {@link groupDevicesByKind}.
* @returns {string}
*/
function logDeviceList(deviceList: IDevicesState['availableDevices']) {
const devicesToStr = (list?: MediaDeviceInfo[]) =>
list?.map(device => `\t\t${device.label}[${device.deviceId}]`).join('\n');
const audioInputs = devicesToStr(deviceList.audioInput);
const audioOutputs = devicesToStr(deviceList.audioOutput);
const videoInputs = devicesToStr(deviceList.videoInput);
logger.debug('Device list updated:\n'
+ `audioInput:\n${audioInputs}\n`
+ `audioOutput:\n${audioOutputs}\n`
+ `videoInput:\n${videoInputs}`);
}
/**
* Implements the middleware of the feature base/devices.
*
@@ -199,7 +179,7 @@ MiddlewareRegistry.register(store => next => action => {
break;
}
case UPDATE_DEVICE_LIST:
logDeviceList(groupDevicesByKind(action.devices));
logDevices(action.devices, 'Device list updated');
if (areDeviceLabelsInitialized(store.getState())) {
return _processPendingRequests(store, next, action);
}

View File

@@ -20,9 +20,8 @@ const browserNameToCheck = {
chromium: browser.isChromiumBased.bind(browser),
electron: browser.isElectron.bind(browser),
firefox: browser.isFirefox.bind(browser),
nwjs: browser.isNWJS.bind(browser),
opera: browser.isOpera.bind(browser),
safari: browser.isSafari.bind(browser)
safari: browser.isSafari.bind(browser),
webkit: browser.isWebKitBased.bind(browser)
};
/**

View File

@@ -186,3 +186,23 @@ export function validateJwt(jwt: string) {
return errors;
}
/**
* Extracts and returns the expiration date of jwt.
*
* @param {string|undefined} jwt - The jwt to check.
* @returns {Date} The expiration date of the jwt.
*/
export function getJwtExpirationDate(jwt: string | undefined) {
if (!jwt) {
return;
}
const payload = jwtDecode(jwt);
if (payload) {
const { exp } = payload;
return new Date(exp * 1000);
}
}

View File

@@ -210,6 +210,5 @@ function _getUserSelectedDeviceId(options: {
return strippedDeviceLabel === strippedCandidateLabel;
});
return foundMatchBasedOnLabel
? foundMatchBasedOnLabel.deviceId : userSelectedDeviceId;
return foundMatchBasedOnLabel?.deviceId;
}

View File

@@ -1,4 +1,6 @@
import React, { ReactNode, useEffect, useLayoutEffect, useRef, useState } from 'react';
import React, { KeyboardEvent, ReactNode,
useCallback, useEffect, useLayoutEffect, useRef, useState } from 'react';
import { FocusOn } from 'react-focus-on';
import { useSelector } from 'react-redux';
import { makeStyles } from 'tss-react/mui';
@@ -9,6 +11,7 @@ import participantsPaneTheme from '../../../components/themes/participantsPaneTh
import { withPixelLineHeight } from '../../../styles/functions.web';
import { spacing } from '../../Tokens';
/**
* Get a style property from a style declaration as a float.
*
@@ -45,6 +48,11 @@ interface IProps {
*/
accessibilityLabel?: string;
/**
* To activate the FocusOn component.
*/
activateFocusTrap?: boolean;
/**
* Children of the context menu.
*/
@@ -163,6 +171,7 @@ const useStyles = makeStyles()(theme => {
const ContextMenu = ({
accessibilityLabel,
activateFocusTrap = false,
children,
className,
entity,
@@ -227,6 +236,133 @@ const ContextMenu = ({
}
}, [ hidden ]);
const handleKeyDown = useCallback((event: KeyboardEvent) => {
const { current: listRef } = containerRef;
const currentFocusElement = document.activeElement;
const moveFocus = (
list: Element | null,
currentFocus: Element | null,
traversalFunction: (
list: Element | null,
currentFocus: Element | null
) => Element | null
) => {
let wrappedOnce = false;
let nextFocus = traversalFunction(list, currentFocus);
/* eslint-disable no-unmodified-loop-condition */
while (list && nextFocus) {
// Prevent infinite loop.
if (nextFocus === list.firstChild) {
if (wrappedOnce) {
return;
}
wrappedOnce = true;
}
// Same logic as useAutocomplete.js
const nextFocusDisabled
/* eslint-disable no-extra-parens */
= (nextFocus as HTMLInputElement).disabled
|| nextFocus.getAttribute('aria-disabled') === 'true';
if (!nextFocus.hasAttribute('tabindex') || nextFocusDisabled) {
// Move to the next element.
nextFocus = traversalFunction(list, nextFocus);
} else {
/* eslint-disable no-extra-parens */
(nextFocus as HTMLElement).focus();
return;
}
}
};
const previousItem = (
list: Element | null,
item: Element | null
): Element | null => {
/**
* To find the last child of the list.
*
* @param {Element | null} element - Element.
* @returns {Element | null}
*/
function lastChild(element: Element | null): Element | null {
while (element?.lastElementChild) {
/* eslint-disable no-param-reassign */
element = element.lastElementChild;
}
return element;
}
if (!list) {
return null;
}
if (list === item) {
return list.lastElementChild;
}
if (item?.previousElementSibling) {
return lastChild(item.previousElementSibling);
}
if (item && item?.parentElement !== list) {
return item.parentElement;
}
return lastChild(list.lastElementChild);
};
const nextItem = (
list: Element | null,
item: Element | null
): Element | null => {
if (!list) {
return null;
}
if (list === item) {
return list.firstElementChild;
}
if (item?.firstElementChild) {
return item.firstElementChild;
}
if (item?.nextElementSibling) {
return item.nextElementSibling;
}
while (item && item.parentElement !== list) {
/* eslint-disable no-param-reassign */
item = item.parentElement;
if (item?.nextElementSibling) {
return item.nextElementSibling;
}
}
return list?.firstElementChild;
};
if (event.key === 'Escape') {
// Close the menu
event.preventDefault();
setIsHidden(true);
} else if (event.key === 'ArrowUp') {
// Move focus to the previous menu item
event.preventDefault();
moveFocus(listRef, currentFocusElement, previousItem);
} else if (event.key === 'ArrowDown') {
// Move focus to the next menu item
event.preventDefault();
moveFocus(listRef, currentFocusElement, nextItem);
}
}, [ containerRef ]);
const removeFocus = useCallback(() => {
onDrawerClose?.();
}, [ onMouseLeave ]);
if (_overflowDrawer && inDrawer) {
return (<div
className = { styles.drawer }
@@ -247,23 +383,32 @@ const ContextMenu = ({
</div>
</Drawer>
</JitsiPortal>
: <div
{ ...aria }
aria-label = { accessibilityLabel }
className = { cx(styles.contextMenu,
: <FocusOn
// Use the `enabled` prop instead of conditionally rendering ReactFocusOn
// to prevent UI stutter on dialog appearance. It seems the focus guards generated annoy
// our DialogPortal positioning calculations.
enabled = { activateFocusTrap && !isHidden }
onClickOutside = { removeFocus }
onEscapeKey = { removeFocus }>
<div
{ ...aria }
aria-label = { accessibilityLabel }
className = { cx(styles.contextMenu,
isHidden && styles.contextMenuHidden,
className
) }
id = { id }
onClick = { onClick }
onKeyDown = { onKeyDown }
onMouseEnter = { onMouseEnter }
onMouseLeave = { onMouseLeave }
ref = { containerRef }
role = { role }
tabIndex = { tabIndex }>
{children}
</div>;
) }
id = { id }
onClick = { onClick }
onKeyDown = { onKeyDown ?? handleKeyDown }
onMouseEnter = { onMouseEnter }
onMouseLeave = { onMouseLeave }
ref = { containerRef }
role = { role }
tabIndex = { tabIndex }>
{children}
</div>
</FocusOn >;
};
export default ContextMenu;

View File

@@ -85,7 +85,7 @@ export interface IProps {
* If no onClick handler is provided, we assume the context menu item is
* not interactive and no role will be set.
*/
role?: 'tab' | 'button';
role?: 'tab' | 'button' | 'menuitem';
/**
* Whether the item is marked as selected.

View File

@@ -18,6 +18,11 @@ interface IProps {
*/
className?: string;
/**
* The breakout name for aria-label.
*/
defaultName?: string;
/**
* Whether or not the actions should be hidden.
*/
@@ -111,7 +116,15 @@ const useStyles = makeStyles()(theme => {
},
highlighted: {
backgroundColor: theme.palette.ui02
backgroundColor: theme.palette.ui02,
'& .actions': {
display: 'flex',
position: 'relative',
top: 'auto',
boxShadow: `-15px 0px 10px -5px ${theme.palette.ui02}`,
backgroundColor: theme.palette.ui02
}
},
detailsContainer: {
@@ -179,6 +192,7 @@ const useStyles = makeStyles()(theme => {
const ListItem = ({
actions,
className,
defaultName,
icon,
id,
hideActions = false,
@@ -229,6 +243,7 @@ const ListItem = ({
return (
<div
aria-label = { defaultName }
className = { cx('list-item-container',
classes.container,
isHighlighted && classes.highlighted,
@@ -237,6 +252,7 @@ const ListItem = ({
data-testid = { testId }
id = { id }
onClick = { onClick }
role = 'listitem'
{ ...(isMobile
? {
onTouchEnd: _onTouchEnd,

View File

@@ -139,7 +139,7 @@ export const commonStyles = (theme: Theme) => {
alignItems: 'center',
background: 'rgba(0,0,0,0.6)',
display: 'flex',
height: '100vh',
height: '100dvh',
justifyContent: 'center',
left: 0,
position: 'absolute' as const,

View File

@@ -69,7 +69,6 @@ const useContextMenu = <T>(): [(force?: boolean | Object) => void,
const menuLeave = useCallback(() => {
isMouseOverMenu.current = false;
lowerMenu();
}, [ lowerMenu ]);
return [ lowerMenu, raiseMenu, toggleMenu, menuEnter, menuLeave, raiseContext ];

View File

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

View File

@@ -90,7 +90,7 @@ const useStyles = makeStyles()(theme => {
zIndex: 300,
'@media (max-width: 580px)': {
height: '100vh',
height: '100dvh',
position: 'fixed',
left: 0,
right: 0,

View File

@@ -153,7 +153,7 @@ class ChromeExtensionBanner extends PureComponent<IProps, IState> {
*/
_isSupportedEnvironment() {
return interfaceConfig.SHOW_CHROME_EXTENSION_BANNER
&& browser.isChrome()
&& browser.isChromiumBased()
&& !browser.isTwa()
&& !isMobileBrowser()
&& !this.props.isVpaas;

View File

@@ -30,7 +30,7 @@ const useStyles = makeStyles()((theme: Theme) => {
container: {
background: '#1E1E1E',
width: '100vw',
height: '100vh',
height: '100dvh',
overflowX: 'hidden',
overflowY: 'auto',
justifyContent: 'center',
@@ -228,6 +228,7 @@ const DeepLinkingMobilePage: React.FC<WithTranslation> = ({ t }) => {
<DialInSummary
className = 'deep-linking-dial-in'
clickableNumbers = { true }
hideError = { true }
room = { room }
url = { url } />
</div>

View File

@@ -10,6 +10,9 @@ import DialInSummary from '../dial-in-summary/web/DialInSummary';
import NoRoomError from './NoRoomError.web';
/**
* TODO: This seems unused, so we can drop it.
*/
document.addEventListener('DOMContentLoaded', () => {
// @ts-ignore
const { room } = parseURLParams(window.location, true, 'search');

View File

@@ -31,6 +31,11 @@ interface IProps extends WithTranslation {
*/
clickableNumbers: boolean;
/**
* Whether to hide the error.
*/
hideError?: boolean;
/**
* The name of the conference to show a conferenceID for.
*/
@@ -93,7 +98,7 @@ const styles = (theme: Theme) => {
color: theme.palette.text01
},
scrollable: {
height: '100vh',
height: '100dvh',
overflowY: 'scroll' as const
},
roomName: {
@@ -167,14 +172,16 @@ class DialInSummary extends Component<IProps, State> {
let contents;
const { conferenceID, error, loading, numbersEnabled } = this.state;
const { classes, showTitle, room, clickableNumbers, scrollable, t } = this.props;
const { classes, hideError, showTitle, room, clickableNumbers, scrollable, t } = this.props;
if (loading) {
contents = '';
} else if (numbersEnabled === false) {
contents = t('info.dialInNotSupported');
} else if (error) {
contents = error;
if (!hideError) {
contents = error;
}
} else {
className = clsx(classes.hasNumbers, scrollable && classes.scrollable);
contents = [

View File

@@ -1,8 +0,0 @@
(global => {
// __filename
if (typeof global.__filename === 'undefined') {
global.__filename = '__filename';
}
})(global || window || this); // eslint-disable-line no-invalid-this

View File

@@ -1,3 +1,2 @@
import './bundler';
import './browser';
import './custom';

View File

@@ -103,7 +103,8 @@ const useStyles = makeStyles()(theme => {
marginRight: '16px',
display: 'flex',
alignItems: 'center',
justifyContent: 'center'
justifyContent: 'center',
border: 'none'
}
};
});
@@ -133,11 +134,15 @@ export const CollapsibleRoom = ({
const overflowDrawer: boolean = useSelector(showOverflowDrawer);
const moderator = useSelector(isLocalParticipantModerator);
const arrow = (<div className = { styles.arrowContainer }>
const arrow = (<button
aria-label = { collapsed ? t('breakoutRooms.hideParticipantList', 'Hide participant list')
: t('breakoutRooms.showParticipantList', 'Show participant list')
}
className = { styles.arrowContainer }>
<Icon
size = { 14 }
src = { collapsed ? IconArrowDown : IconArrowUp } />
</div>);
</button>);
const roomName = (<span className = { styles.roomName }>
{`${room.name || t('breakoutRooms.mainRoom')} (${Object.keys(room?.participants
@@ -155,6 +160,8 @@ export const CollapsibleRoom = ({
<ListItem
actions = { children }
className = { cx(styles.container, 'breakout-room-container') }
defaultName = { `${room.name || t('breakoutRooms.mainRoom')} (${Object.keys(room?.participants
|| {}).length})` }
icon = { arrow }
isHighlighted = { isHighlighted }
onClick = { toggleCollapsed }

View File

@@ -107,6 +107,7 @@ export const RoomContextMenu = ({
return (
<ContextMenu
activateFocusTrap = { true }
entity = { room }
isDrawerOpen = { Boolean(room) }
offsetTarget = { offsetTarget }

View File

@@ -1,4 +1,5 @@
import React, { useCallback } from 'react';
import React, { useCallback, useEffect } from 'react';
import { useTranslation } from 'react-i18next';
import { useSelector } from 'react-redux';
import { makeStyles } from 'tss-react/mui';
@@ -14,7 +15,6 @@ import {
isInBreakoutRoom
} from '../../../../../breakout-rooms/functions';
import { IRoom } from '../../../../../breakout-rooms/types';
import { showOverflowDrawer } from '../../../../../toolbox/functions.web';
import { AutoAssignButton } from './AutoAssignButton';
import { CollapsibleRoom } from './CollapsibleRoom';
@@ -42,6 +42,7 @@ const useStyles = makeStyles()(theme => {
export const RoomList = ({ searchString }: IProps) => {
const { classes } = useStyles();
const { t } = useTranslation();
const currentRoomId = useSelector(getCurrentRoomId);
const rooms = Object.values(useSelector(getBreakoutRooms, equals))
.filter((room: IRoom) => room.id !== currentRoomId)
@@ -50,7 +51,6 @@ export const RoomList = ({ searchString }: IProps) => {
const isLocalModerator = useSelector(isLocalParticipantModerator);
const showAutoAssign = useSelector(isAutoAssignParticipantsVisible);
const { hideJoinRoomButton } = useSelector(getBreakoutRoomsConfig);
const overflowDrawer = useSelector(showOverflowDrawer);
const [ lowerMenu, raiseMenu, toggleMenu, menuEnter, menuLeave, raiseContext ] = useContextMenu<IRoom>();
const [ lowerParticipantMenu, raiseParticipantMenu, toggleParticipantMenu,
participantMenuEnter, participantMenuLeave, raiseParticipantContext ] = useContextMenu<{
@@ -58,21 +58,28 @@ export const RoomList = ({ searchString }: IProps) => {
participantName: string;
room: IRoom;
}>();
const hideMenu = useCallback(() => !overflowDrawer && lowerMenu(), [ overflowDrawer, lowerMenu ]);
const onRaiseMenu = useCallback(room => (target: HTMLElement) => raiseMenu(room, target), [ raiseMenu ]);
// close the menu when the room vanishes
useEffect(() => {
if (raiseContext.entity && !rooms.some(r => r.id === raiseContext.entity?.id)) {
lowerMenu();
}
}, [ raiseContext, rooms, lowerMenu ]);
return (
<>
{inBreakoutRoom && <LeaveButton className = { classes.topMargin } />}
{showAutoAssign && <AutoAssignButton className = { classes.topMargin } />}
<div
aria-label = { t('breakoutRooms.breakoutList', 'breakout list') }
className = { classes.topMargin }
id = 'breakout-rooms-list'>
id = 'breakout-rooms-list'
role = 'list'>
{rooms.map(room => (
<React.Fragment key = { room.id }>
<CollapsibleRoom
isHighlighted = { raiseContext.entity === room }
onLeave = { hideMenu }
isHighlighted = { true }
onRaiseMenu = { onRaiseMenu(room) }
participantContextEntity = { raiseParticipantContext.entity }
raiseParticipantContextMenu = { raiseParticipantMenu }

View File

@@ -29,7 +29,7 @@ import { withPixelLineHeight } from '../../../base/styles/functions.web';
import ContextMenu from '../../../base/ui/components/web/ContextMenu';
import ContextMenuItemGroup from '../../../base/ui/components/web/ContextMenuItemGroup';
import { isInBreakoutRoom } from '../../../breakout-rooms/functions';
import { openSettingsDialog } from '../../../settings/actions';
import { openSettingsDialog } from '../../../settings/actions.web';
import { SETTINGS_TABS } from '../../../settings/constants';
import { shouldShowModeratorSettings } from '../../../settings/functions.web';
import MuteEveryonesVideoDialog from '../../../video-menu/components/web/MuteEveryonesVideoDialog';
@@ -133,6 +133,7 @@ export const FooterContextMenu = ({ isOpen, onDrawerClose, onMouseLeave }: IProp
return (
<ContextMenu
activateFocusTrap = { true }
className = { classes.contextMenu }
hidden = { !isOpen }
isDrawerOpen = { isOpen }

View File

@@ -48,7 +48,7 @@ const useStyles = makeStyles()(theme => {
},
'@media (max-width: 580px)': {
height: '100vh',
height: '100dvh',
position: 'fixed',
left: 0,
right: 0,

View File

@@ -3,6 +3,7 @@ import React, { useCallback, useEffect, useLayoutEffect, useState } from 'react'
import { useTranslation } from 'react-i18next';
import {
BackHandler,
Platform,
StyleProp,
Text,
TextStyle,
@@ -18,6 +19,7 @@ import { getConferenceName } from '../../../base/conference/functions';
import { connect } from '../../../base/connection/actions.native';
import { PREJOIN_PAGE_HIDE_DISPLAY_NAME } from '../../../base/flags/constants';
import { getFeatureFlag } from '../../../base/flags/functions';
import { IconCloseLarge } from '../../../base/icons/svg';
import JitsiScreen from '../../../base/modal/components/JitsiScreen';
import { getLocalParticipant } from '../../../base/participants/functions';
import { getFieldValue } from '../../../base/react/functions';
@@ -28,7 +30,7 @@ import Input from '../../../base/ui/components/native/Input';
import { BUTTON_TYPES } from '../../../base/ui/constants.native';
import BrandingImageBackground from '../../../dynamic-branding/components/native/BrandingImageBackground';
import LargeVideo from '../../../large-video/components/LargeVideo.native';
import { screenHeaderCloseButton } from '../../../mobile/navigation/functions';
import HeaderNavigationButton from '../../../mobile/navigation/components/HeaderNavigationButton';
import { navigateRoot } from '../../../mobile/navigation/rootNavigationContainerRef';
import { screen } from '../../../mobile/navigation/routes';
import AudioMuteButton from '../../../toolbox/components/native/AudioMuteButton';
@@ -92,9 +94,25 @@ const Prejoin: React.FC<IPrejoinProps> = ({ navigation }: IPrejoinProps) => {
}, []);
const headerLeft = () => {
if (Platform.OS === 'ios') {
return (
<HeaderNavigationButton
label = { t('dialog.close') }
onPress = { goBack } />
);
}
return (
<HeaderNavigationButton
onPress = { goBack }
src = { IconCloseLarge } />
);
};
useLayoutEffect(() => {
navigation.setOptions({
headerLeft: () => screenHeaderCloseButton(goBack),
headerLeft,
headerTitle: t('prejoin.joinMeeting')
});
}, [ navigation ]);

View File

@@ -1,19 +1,26 @@
import React, { useCallback, useLayoutEffect } from 'react';
import { useTranslation } from 'react-i18next';
import { StyleProp, Text, TextStyle, View, ViewStyle } from 'react-native';
import {
Platform,
StyleProp,
Text,
TextStyle,
View,
ViewStyle
} from 'react-native';
import { useDispatch, useSelector } from 'react-redux';
import { appNavigate } from '../../../app/actions.native';
import { IReduxState } from '../../../app/types';
import { getConferenceName } from '../../../base/conference/functions';
import Icon from '../../../base/icons/components/Icon';
import { IconWarning } from '../../../base/icons/svg';
import { IconCloseLarge, IconWarning } from '../../../base/icons/svg';
import JitsiScreen from '../../../base/modal/components/JitsiScreen';
import { ASPECT_RATIO_NARROW } from '../../../base/responsive-ui/constants';
import Button from '../../../base/ui/components/native/Button';
import { BUTTON_TYPES } from '../../../base/ui/constants.native';
import getUnsafeRoomText from '../../../base/util/getUnsafeRoomText.native';
import { screenHeaderCloseButton } from '../../../mobile/navigation/functions';
import HeaderNavigationButton from '../../../mobile/navigation/components/HeaderNavigationButton';
import { navigateRoot } from '../../../mobile/navigation/rootNavigationContainerRef';
import { screen } from '../../../mobile/navigation/routes';
import { IPrejoinProps } from '../../types';
@@ -42,9 +49,25 @@ const UnsafeRoomWarning: React.FC<IPrejoinProps> = ({ navigation }: IPrejoinProp
return true;
}, [ dispatch ]);
const headerLeft = () => {
if (Platform.OS === 'ios') {
return (
<HeaderNavigationButton
label = { t('dialog.close') }
onPress = { goBack } />
);
}
return (
<HeaderNavigationButton
onPress = { goBack }
src = { IconCloseLarge } />
);
};
useLayoutEffect(() => {
navigation.setOptions({
headerLeft: () => screenHeaderCloseButton(goBack),
headerLeft,
headerTitle: t('prejoin.joinMeeting')
});
}, [ navigation ]);

View File

@@ -33,7 +33,7 @@ export function isScreenAudioShared(state: IReduxState) {
* @returns {boolean}
*/
export function isScreenAudioSupported() {
return (!isMobileBrowser() && browser.isChrome()) || (browser.isElectron() && isWindows());
return (!isMobileBrowser() && browser.isChromiumBased()) || (browser.isElectron() && isWindows());
}
/**

View File

@@ -30,7 +30,7 @@ export function openLogoutDialog() {
dispatch(hangup(true));
} else {
conference?.room.moderator.logout(() => dispatch(hangup(true)));
conference?.room.xmpp.moderator.logout(() => dispatch(hangup(true)));
}
}
}));

View File

@@ -69,7 +69,7 @@ export function openLogoutDialog() {
return;
}
conference?.room.moderator.logout(() => dispatch(hangup(true)));
conference?.room.xmpp.moderator.logout(() => dispatch(hangup(true)));
}
}
}));

View File

@@ -98,7 +98,7 @@ const useStyles = makeStyles()(theme => {
right: 'auto',
margin: 0,
marginBottom: theme.spacing(1),
maxHeight: 'calc(100vh - 100px)',
maxHeight: 'calc(100dvh - 100px)',
overflow: 'auto',
width: '300px'
},

View File

@@ -13,7 +13,7 @@ import Checkbox from '../../../../base/ui/components/web/Checkbox';
import ContextMenu from '../../../../base/ui/components/web/ContextMenu';
import ContextMenuItem from '../../../../base/ui/components/web/ContextMenuItem';
import ContextMenuItemGroup from '../../../../base/ui/components/web/ContextMenuItemGroup';
import { checkBlurSupport } from '../../../../virtual-background/functions';
import { checkBlurSupport, checkVirtualBackgroundEnabled } from '../../../../virtual-background/functions';
import { openSettingsDialog } from '../../../actions';
import { SETTINGS_TABS } from '../../../constants';
import { createLocalVideoTracks } from '../../../functions.web';
@@ -57,12 +57,17 @@ export interface IProps {
* All the camera device ids currently connected.
*/
videoDeviceIds: string[];
/**
* Whether or not the virtual background is visible.
*/
visibleVirtualBackground: boolean;
}
const useStyles = makeStyles()(theme => {
return {
container: {
maxHeight: 'calc(100vh - 100px)',
maxHeight: 'calc(100dvh - 100px)',
overflow: 'auto',
margin: 0,
marginBottom: theme.spacing(1),
@@ -145,7 +150,8 @@ const VideoSettingsContent = ({
selectBackground,
setVideoInputDevice,
toggleVideoSettings,
videoDeviceIds
videoDeviceIds,
visibleVirtualBackground
}: IProps) => {
const _componentWasUnmounted = useRef(false);
const [ trackData, setTrackData ] = useState(new Array(videoDeviceIds.length).fill({
@@ -287,8 +293,6 @@ const VideoSettingsContent = ({
}
}, [ videoDeviceIds ]);
const virtualBackgroundSupported = checkBlurSupport();
return (
<ContextMenu
aria-labelledby = 'video-settings-button'
@@ -301,7 +305,7 @@ const VideoSettingsContent = ({
{trackData.map((data, i) => _renderPreviewEntry(data, i))}
</ContextMenuItemGroup>
<ContextMenuItemGroup>
{ virtualBackgroundSupported && <ContextMenuItem
{ visibleVirtualBackground && <ContextMenuItem
accessibilityLabel = { t('virtualBackground.title') }
icon = { IconImage }
onClick = { selectBackground }
@@ -323,7 +327,9 @@ const mapStateToProps = (state: IReduxState) => {
const { localFlipX } = state['features/base/settings'];
return {
localFlipX: Boolean(localFlipX)
localFlipX: Boolean(localFlipX),
visibleVirtualBackground: checkBlurSupport()
&& checkVirtualBackgroundEnabled(state)
};
};

View File

@@ -37,7 +37,7 @@ interface IProps {
const useStyles = makeStyles()(theme => {
return {
drawerMenuContainer: {
height: '100vh',
height: '100dvh',
display: 'flex',
alignItems: 'flex-end'
},

View File

@@ -80,7 +80,7 @@ const useStyles = makeStyles<{ overflowDrawer: boolean; reactionsMenuHeight: num
right: 'auto',
margin: 0,
marginBottom: '8px',
maxHeight: overflowDrawer ? undefined : 'calc(100vh - 100px)',
maxHeight: overflowDrawer ? undefined : 'calc(100dvh - 100px)',
paddingBottom: overflowDrawer ? undefined : 0,
minWidth: '240px',
overflow: 'hidden'
@@ -88,7 +88,7 @@ const useStyles = makeStyles<{ overflowDrawer: boolean; reactionsMenuHeight: num
content: {
position: 'relative',
maxHeight: overflowDrawer
? `calc(100% - ${reactionsMenuHeight}px - 16px)` : `calc(100vh - 100px - ${reactionsMenuHeight}px)`,
? `calc(100% - ${reactionsMenuHeight}px - 16px)` : `calc(100dvh - 100px - ${reactionsMenuHeight}px)`,
overflowY: 'auto'
},
footer: {

View File

@@ -153,7 +153,7 @@ const useStyles = makeStyles()(() => {
right: 'auto',
margin: 0,
marginBottom: '8px',
maxHeight: 'calc(100vh - 100px)',
maxHeight: 'calc(100dvh - 100px)',
minWidth: '240px'
},

View File

@@ -36,7 +36,7 @@ export const NOT_APPLICABLE = 'N/A';
export const TOOLBAR_TIMEOUT = 4000;
export const DRAWER_MAX_HEIGHT = '80vh - 64px';
export const DRAWER_MAX_HEIGHT = '80dvh - 64px';
export const NOTIFY_CLICK_MODE = {
ONLY_NOTIFY: 'ONLY_NOTIFY',

View File

@@ -32,11 +32,6 @@ interface IProps extends WithTranslation {
*/
_images: Array<Image>;
/**
* The current local flip x status.
*/
_localFlipX: boolean;
/**
* Whether or not multi-stream send support is enabled.
*/
@@ -47,24 +42,11 @@ interface IProps extends WithTranslation {
*/
_showUploadButton: boolean;
/**
* Returns the selected virtual background object.
*/
_virtualBackground: any;
/**
* The redux {@code dispatch} function.
*/
dispatch: IStore['dispatch'];
/**
* The initial options copied in the state for the {@code VirtualBackground} component.
*
* NOTE: currently used only for electron in order to open the dialog in the correct state after desktop sharing
* selection.
*/
initialOptions?: Object;
/**
* Options change handler.
*/
@@ -205,13 +187,9 @@ const useStyles = makeStyles()(theme => {
*/
function VirtualBackgrounds({
_images,
_localFlipX,
selectedThumbnail,
_showUploadButton,
_virtualBackground,
onOptionsChange,
options,
initialOptions,
selectedVideoInputId,
t
}: IProps) {
@@ -221,10 +199,6 @@ function VirtualBackgrounds({
const [ storedImages, setStoredImages ] = useState<Array<Image>>((localImages && safeJsonParse(localImages)) || []);
const [ loading, setLoading ] = useState(false);
useEffect(() => {
onOptionsChange({ ...initialOptions });
}, []);
const deleteStoredImage = useCallback(e => {
const imageId = e.currentTarget.getAttribute('data-imageid');
@@ -377,7 +351,11 @@ function VirtualBackgrounds({
return acc;
}, {})
};
const currentBackgroundLabel = labelsMap[selectedThumbnail] || labelsMap.none;
const currentBackgroundLabel = options?.selectedThumbnail ? labelsMap[options.selectedThumbnail] : labelsMap.none;
const isThumbnailSelected = useCallback(thumbnail => options?.selectedThumbnail === thumbnail, [ options ]);
const getSelectedThumbnailClass = useCallback(
thumbnail => isThumbnailSelected(thumbnail) && classes.selectedThumbnail, [ isThumbnailSelected, options ]
);
return (
<>
@@ -415,10 +393,10 @@ function VirtualBackgrounds({
content = { t('virtualBackground.removeBackground') }
position = { 'top' }>
<div
aria-checked = { selectedThumbnail === 'none' }
aria-checked = { isThumbnailSelected('none') }
aria-label = { t('virtualBackground.removeBackground') }
className = { cx(classes.thumbnail, classes.noneThumbnail,
selectedThumbnail === 'none' && classes.selectedThumbnail) }
getSelectedThumbnailClass('none')) }
onClick = { removeBackground }
onKeyPress = { removeBackgroundKeyPress }
role = 'radio'
@@ -430,10 +408,10 @@ function VirtualBackgrounds({
content = { t('virtualBackground.slightBlur') }
position = { 'top' }>
<div
aria-checked = { selectedThumbnail === 'slight-blur' }
aria-checked = { isThumbnailSelected('slight-blur') }
aria-label = { t('virtualBackground.slightBlur') }
className = { cx(classes.thumbnail, classes.slightBlur,
selectedThumbnail === 'slight-blur' && classes.selectedThumbnail) }
getSelectedThumbnailClass('slight-blur')) }
onClick = { enableSlideBlur }
onKeyPress = { enableSlideBlurKeyPress }
role = 'radio'
@@ -445,10 +423,10 @@ function VirtualBackgrounds({
content = { t('virtualBackground.blur') }
position = { 'top' }>
<div
aria-checked = { selectedThumbnail === 'blur' }
aria-checked = { isThumbnailSelected('blur') }
aria-label = { t('virtualBackground.blur') }
className = { cx(classes.thumbnail, classes.blur,
selectedThumbnail === 'blur' && classes.selectedThumbnail) }
getSelectedThumbnailClass('blur')) }
onClick = { enableBlur }
onKeyPress = { enableBlurKeyPress }
role = 'radio'
@@ -463,11 +441,9 @@ function VirtualBackgrounds({
position = { 'top' }>
<img
alt = { image.tooltip && t(`virtualBackground.${image.tooltip}`) }
aria-checked = { options?.selectedThumbnail === image.id
|| selectedThumbnail === image.id }
aria-checked = { isThumbnailSelected(image.id) }
className = { cx(classes.thumbnail,
(options?.selectedThumbnail === image.id
|| selectedThumbnail === image.id) && classes.selectedThumbnail) }
getSelectedThumbnailClass(image.id)) }
data-imageid = { image.id }
onClick = { setImageBackground }
onError = { onError }
@@ -483,9 +459,9 @@ function VirtualBackgrounds({
key = { image.id }>
<img
alt = { t('virtualBackground.uploadedImage', { index: index + 1 }) }
aria-checked = { selectedThumbnail === image.id }
aria-checked = { isThumbnailSelected(image.id) }
className = { cx(classes.thumbnail,
selectedThumbnail === image.id && classes.selectedThumbnail) }
getSelectedThumbnailClass(image.id)) }
data-imageid = { image.id }
onClick = { setUploadedImageBackground }
onError = { onError }
@@ -522,14 +498,11 @@ function VirtualBackgrounds({
* @returns {{Props}}
*/
function _mapStateToProps(state: IReduxState) {
const { localFlipX } = state['features/base/settings'];
const dynamicBrandingImages = state['features/dynamic-branding'].virtualBackgrounds;
const hasBrandingImages = Boolean(dynamicBrandingImages.length);
return {
_localFlipX: Boolean(localFlipX),
_images: (hasBrandingImages && dynamicBrandingImages) || IMAGES,
_virtualBackground: state['features/virtual-background'],
_showUploadButton: !state['features/base/config'].disableAddingBackgroundImages,
_multiStreamModeEnabled: getMultipleVideoSendingSupportFeatureFlag(state)
};

View File

@@ -81,7 +81,7 @@ module:hook('muc-occupant-pre-join', function (event)
local room, stanza = event.room, event.stanza;
-- we skip processing only if jicofo_lock is set to false
if room._data.jicofo_lock == false or is_healthcheck_room(stanza.attr.from) then
if room._data.jicofo_lock == false or is_healthcheck_room(room.jid) then
return;
end

View File

@@ -8,6 +8,7 @@
-- This module depends on mod_persistent_lobby.
local um_is_admin = require 'core.usermanager'.is_admin;
local jid = require 'util.jid';
local is_healthcheck_room = module:require 'util'.is_healthcheck_room;
local disable_auto_owners = module:get_option_boolean('wait_for_host_disable_auto_owners', false);
@@ -47,7 +48,7 @@ module:hook('muc-occupant-pre-join', function (event)
-- we ignore jicofo as we want it to join the room or if the room has already seen its
-- authenticated host
if is_admin(occupant.bare_jid) or room.has_host then
if is_admin(occupant.bare_jid) or is_healthcheck_room(room.jid) or room.has_host then
return;
end
@@ -70,6 +71,7 @@ module:hook('muc-occupant-pre-join', function (event)
module:log('info', 'Host %s arrived in %s.', occupant.bare_jid, room.jid);
audit_logger('room_jid:%s created_by:%s', room.jid,
session.jitsi_meet_context_user and session.jitsi_meet_context_user.id or 'nil');
module:fire_event('room_host_arrived', room.jid, session);
lobby_host:fire_event('destroy-lobby-room', {
room = room,
newjid = room.jid,

View File

@@ -144,7 +144,7 @@ run_when_component_loaded(lobby_muc_component_host, function(host_module, host_n
local lobby_room = event.room;
local main_room = lobby_room.main_room;
if is_healthcheck_room(main_room.jid) or not has_persistent_lobby(main_room) then
if not main_room or is_healthcheck_room(main_room.jid) or not has_persistent_lobby(main_room) then
return;
end

View File

@@ -381,7 +381,13 @@ function Util:verify_room(session, room_address)
end
module:log("debug", "room to check: %s", room_to_check)
if not room_to_check then
return false
if not self.requireRoomClaim then
-- if we do not require to have the room claim, and it is missing
-- there is no point of continue and verifying the roomName and the tenant
return true;
end
return false;
end
end