mirror of
https://gitcode.com/GitHub_Trending/ji/jitsi-meet.git
synced 2026-09-09 20:28:40 +00:00
Compare commits
35 Commits
7529
...
jitsi-meet
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7bf3e7df1d | ||
|
|
f9ac965e18 | ||
|
|
d70412166c | ||
|
|
a843406cb0 | ||
|
|
58115477a2 | ||
|
|
e1dc573c3c | ||
|
|
c025102511 | ||
|
|
54d052de73 | ||
|
|
7e633f0136 | ||
|
|
4b4bc1c823 | ||
|
|
767e23f34c | ||
|
|
b003d28cc5 | ||
|
|
91c8e9bd86 | ||
|
|
1f52c0b49f | ||
|
|
16fd4d4411 | ||
|
|
b8a669ad21 | ||
|
|
f0cb33a627 | ||
|
|
b5b7019325 | ||
|
|
7ccd68eb18 | ||
|
|
44b0ac57eb | ||
|
|
a411b7c969 | ||
|
|
fc8ce532f6 | ||
|
|
ef56b3c5b6 | ||
|
|
37e13804a5 | ||
|
|
8b209b3c6e | ||
|
|
cb26042d08 | ||
|
|
2952d1cde8 | ||
|
|
8a7f456560 | ||
|
|
f74b6cd82f | ||
|
|
d04515c35a | ||
|
|
2aca0ce110 | ||
|
|
d0e49b27a1 | ||
|
|
d97c365aed | ||
|
|
8304e77a04 | ||
|
|
b1db315582 |
@@ -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')
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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'
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
},
|
||||
|
||||
|
||||
28
config.js
28
config.js
@@ -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
|
||||
*/
|
||||
|
||||
/**
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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%;
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
.deep-linking-mobile {
|
||||
background-color: #fff;
|
||||
height: 100vh;
|
||||
height: 100dvh;
|
||||
overflow: auto;
|
||||
position: relative;
|
||||
width: 100vw;
|
||||
|
||||
2
debian/jitsi-meet-tokens.postinst
vendored
2
debian/jitsi-meet-tokens.postinst
vendored
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
10
metadata/en-US/full_description.txt
Normal file
10
metadata/en-US/full_description.txt
Normal 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.
|
||||
BIN
metadata/en-US/images/icon.png
Normal file
BIN
metadata/en-US/images/icon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 12 KiB |
BIN
metadata/en-US/images/phoneScreenshots/01.png
Normal file
BIN
metadata/en-US/images/phoneScreenshots/01.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 302 KiB |
BIN
metadata/en-US/images/phoneScreenshots/02.png
Normal file
BIN
metadata/en-US/images/phoneScreenshots/02.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 168 KiB |
BIN
metadata/en-US/images/phoneScreenshots/03.png
Normal file
BIN
metadata/en-US/images/phoneScreenshots/03.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 243 KiB |
BIN
metadata/en-US/images/sevenInchScreenshots/01.png
Normal file
BIN
metadata/en-US/images/sevenInchScreenshots/01.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 928 KiB |
BIN
metadata/en-US/images/sevenInchScreenshots/02.png
Normal file
BIN
metadata/en-US/images/sevenInchScreenshots/02.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 534 KiB |
1
metadata/en-US/short_description.txt
Normal file
1
metadata/en-US/short_description.txt
Normal file
@@ -0,0 +1 @@
|
||||
Secure, Simple and Scalable Video Conferences with state-of-the-art video quality
|
||||
11
modules/API/external/external_api.js
vendored
11
modules/API/external/external_api.js
vendored
@@ -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);
|
||||
|
||||
@@ -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
157
package-lock.json
generated
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
112
react-native-sdk/package-lock.json
generated
112
react-native-sdk/package-lock.json
generated
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
11
react-native-sdk/update_dependencies.js
vendored
11
react-native-sdk/update_dependencies.js
vendored
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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 => {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -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();
|
||||
};
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
};
|
||||
}
|
||||
|
||||
@@ -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 }));
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
};
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
48
react/features/authentication/functions.any.ts
Normal file
48
react/features/authentication/functions.any.ts
Normal 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;
|
||||
};
|
||||
48
react/features/authentication/functions.native.ts
Normal file
48
react/features/authentication/functions.native.ts
Normal 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));
|
||||
};
|
||||
@@ -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);
|
||||
};
|
||||
92
react/features/authentication/functions.web.ts
Normal file
92
react/features/authentication/functions.web.ts
Normal 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);
|
||||
};
|
||||
@@ -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));
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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([]);
|
||||
|
||||
8
react/features/base/devices/constants.ts
Normal file
8
react/features/base/devices/constants.ts
Normal 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' ];
|
||||
@@ -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.
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -210,6 +210,5 @@ function _getUserSelectedDeviceId(options: {
|
||||
return strippedDeviceLabel === strippedCandidateLabel;
|
||||
});
|
||||
|
||||
return foundMatchBasedOnLabel
|
||||
? foundMatchBasedOnLabel.deviceId : userSelectedDeviceId;
|
||||
return foundMatchBasedOnLabel?.deviceId;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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 ];
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -90,7 +90,7 @@ const useStyles = makeStyles()(theme => {
|
||||
zIndex: 300,
|
||||
|
||||
'@media (max-width: 580px)': {
|
||||
height: '100vh',
|
||||
height: '100dvh',
|
||||
position: 'fixed',
|
||||
left: 0,
|
||||
right: 0,
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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');
|
||||
|
||||
@@ -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 = [
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
(global => {
|
||||
|
||||
// __filename
|
||||
if (typeof global.__filename === 'undefined') {
|
||||
global.__filename = '__filename';
|
||||
}
|
||||
|
||||
})(global || window || this); // eslint-disable-line no-invalid-this
|
||||
@@ -1,3 +1,2 @@
|
||||
import './bundler';
|
||||
import './browser';
|
||||
import './custom';
|
||||
|
||||
@@ -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 }
|
||||
|
||||
@@ -107,6 +107,7 @@ export const RoomContextMenu = ({
|
||||
|
||||
return (
|
||||
<ContextMenu
|
||||
activateFocusTrap = { true }
|
||||
entity = { room }
|
||||
isDrawerOpen = { Boolean(room) }
|
||||
offsetTarget = { offsetTarget }
|
||||
|
||||
@@ -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 }
|
||||
|
||||
@@ -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 }
|
||||
|
||||
@@ -48,7 +48,7 @@ const useStyles = makeStyles()(theme => {
|
||||
},
|
||||
|
||||
'@media (max-width: 580px)': {
|
||||
height: '100vh',
|
||||
height: '100dvh',
|
||||
position: 'fixed',
|
||||
left: 0,
|
||||
right: 0,
|
||||
|
||||
@@ -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 ]);
|
||||
|
||||
@@ -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 ]);
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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)));
|
||||
}
|
||||
}
|
||||
}));
|
||||
|
||||
@@ -69,7 +69,7 @@ export function openLogoutDialog() {
|
||||
return;
|
||||
}
|
||||
|
||||
conference?.room.moderator.logout(() => dispatch(hangup(true)));
|
||||
conference?.room.xmpp.moderator.logout(() => dispatch(hangup(true)));
|
||||
}
|
||||
}
|
||||
}));
|
||||
|
||||
@@ -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'
|
||||
},
|
||||
|
||||
@@ -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)
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
@@ -37,7 +37,7 @@ interface IProps {
|
||||
const useStyles = makeStyles()(theme => {
|
||||
return {
|
||||
drawerMenuContainer: {
|
||||
height: '100vh',
|
||||
height: '100dvh',
|
||||
display: 'flex',
|
||||
alignItems: 'flex-end'
|
||||
},
|
||||
|
||||
@@ -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: {
|
||||
|
||||
@@ -153,7 +153,7 @@ const useStyles = makeStyles()(() => {
|
||||
right: 'auto',
|
||||
margin: 0,
|
||||
marginBottom: '8px',
|
||||
maxHeight: 'calc(100vh - 100px)',
|
||||
maxHeight: 'calc(100dvh - 100px)',
|
||||
minWidth: '240px'
|
||||
},
|
||||
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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)
|
||||
};
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
Reference in New Issue
Block a user