mirror of
https://gitcode.com/GitHub_Trending/ji/jitsi-meet.git
synced 2026-09-09 13:18:40 +00:00
Compare commits
63 Commits
7506
...
jitsi-meet
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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 | ||
|
|
4e785dd982 | ||
|
|
40f5afcf43 | ||
|
|
de2688bb33 | ||
|
|
76db09303b | ||
|
|
ea4e20f9a7 | ||
|
|
01a74856a3 | ||
|
|
36045100bf | ||
|
|
cc344cb548 | ||
|
|
a2624952a0 | ||
|
|
b8259e00dc | ||
|
|
463c823d3b | ||
|
|
5a6f3ead5a | ||
|
|
1b4d666af3 | ||
|
|
77d299338a | ||
|
|
33fc6e2f3f | ||
|
|
a95eaa6c2e | ||
|
|
5a3947bb23 | ||
|
|
f84a561d9e | ||
|
|
295878ffff | ||
|
|
609942654a | ||
|
|
60ad0196c3 | ||
|
|
caea6966ef | ||
|
|
d4c269f7cb | ||
|
|
54a1ee53b4 | ||
|
|
2c51e8ac06 | ||
|
|
3cbd69eef2 | ||
|
|
ee539644d8 | ||
|
|
465263bc97 | ||
|
|
1def65eb90 | ||
|
|
746be98bfc | ||
|
|
99b58dd318 | ||
|
|
df3ef0d895 | ||
|
|
83e4042668 | ||
|
|
c6e87568b6 | ||
|
|
0170c65c7b |
@@ -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();
|
||||
|
||||
@@ -10,29 +10,24 @@ buildscript {
|
||||
mavenCentral()
|
||||
}
|
||||
dependencies {
|
||||
classpath 'com.android.tools.build:gradle:7.1.1'
|
||||
classpath 'com.google.gms:google-services:4.3.14'
|
||||
classpath 'com.google.firebase:firebase-crashlytics-gradle:2.9.2'
|
||||
classpath 'com.android.tools.build:gradle:7.3.1'
|
||||
classpath 'com.google.gms:google-services:4.4.0'
|
||||
classpath 'com.google.firebase:firebase-crashlytics-gradle:2.9.9'
|
||||
}
|
||||
}
|
||||
|
||||
ext {
|
||||
kotlinVersion = "1.7.0"
|
||||
buildToolsVersion = "31.0.0"
|
||||
compileSdkVersion = 32
|
||||
buildToolsVersion = "33.0.2"
|
||||
compileSdkVersion = 33
|
||||
minSdkVersion = 24
|
||||
targetSdkVersion = 32
|
||||
targetSdkVersion = 33
|
||||
supportLibVersion = "28.0.0"
|
||||
|
||||
if (System.properties['os.arch'] == "aarch64") {
|
||||
// For M1 Users we need to use the NDK 24 which added support for aarch64
|
||||
ndkVersion = "24.0.8215888"
|
||||
} else {
|
||||
// Otherwise we default to the side-by-side NDK version from AGP.
|
||||
ndkVersion = "21.4.7075529"
|
||||
}
|
||||
// We use NDK 23 which has both M1 support and is the side-by-side NDK version from AGP.
|
||||
ndkVersion = "23.1.7779620"
|
||||
|
||||
// The Maven artifact groupdId of the third-party react-native modules which
|
||||
// The Maven artifact groupId of the third-party react-native modules which
|
||||
// Jitsi Meet SDK for Android depends on and which are not available in
|
||||
// third-party Maven repositories so we have to deploy to a Maven repository
|
||||
// of ours.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.3.3-all.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.5.1-all.zip
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
|
||||
44
config.js
44
config.js
@@ -588,7 +588,7 @@ var config = {
|
||||
// },
|
||||
|
||||
// Configs for the lobby screen.
|
||||
// lobby {
|
||||
// lobby: {
|
||||
// // If Lobby is enabled, it starts knocking automatically. Replaces `autoKnockLobby`.
|
||||
// autoKnock: false,
|
||||
// // Enables the lobby chat. Replaces `enableLobbyChat`.
|
||||
@@ -1015,6 +1015,10 @@ var config = {
|
||||
// The Amplitude APP Key:
|
||||
// amplitudeAPPKey: '<APP_KEY>',
|
||||
|
||||
// Enables Amplitude UTM tracking:
|
||||
// Default value is false.
|
||||
// amplitudeIncludeUTM: false,
|
||||
|
||||
// Obfuscates room name sent to analytics (amplitude, rtcstats)
|
||||
// Default value is false.
|
||||
// obfuscateRoomName: false,
|
||||
@@ -1294,6 +1298,16 @@ var config = {
|
||||
// A list of images that can be used as video backgrounds.
|
||||
// When this field is present, the default images will be replaced with those provided.
|
||||
virtualBackgrounds: ['https://example.com/img.jpg'],
|
||||
// Object containing customized icons that should replace the default ones.
|
||||
// The keys need to be the exact same icon names used in here:
|
||||
// https://github.com/jitsi/jitsi-meet/blob/master/react/features/base/icons/svg/index.ts
|
||||
// To avoid having the icons trimmed or displayed in an unexpected way, please provide svg
|
||||
// files containing svg xml icons in the size that the default icons come in.
|
||||
customIcons: {
|
||||
IconArrowUp: 'https://example.com/arrow-up.svg',
|
||||
IconDownload: 'https://example.com/download.svg',
|
||||
IconRemoteControlStart: 'https://example.com/remote-start.svg',
|
||||
},
|
||||
// Object containing a theme's properties. It also supports partial overwrites of the main theme.
|
||||
// For a list of all possible theme tokens and their current defaults, please check:
|
||||
// https://github.com/jitsi/jitsi-meet/tree/master/resources/custom-theme/custom-theme.json
|
||||
@@ -1427,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
|
||||
@@ -1446,9 +1485,6 @@ var config = {
|
||||
peopleSearchQueryTypes
|
||||
peopleSearchUrl
|
||||
requireDisplayName
|
||||
tokenAuthUrl
|
||||
tokenAuthUrlAutoRedirect
|
||||
tokenLogoutUrl
|
||||
*/
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,62 +0,0 @@
|
||||
.drawer-portal {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
z-index: 351;
|
||||
border-radius: 16px 16px 0 0;
|
||||
|
||||
&.notification-portal {
|
||||
z-index: 901;
|
||||
}
|
||||
}
|
||||
|
||||
.drawer-portal::after {
|
||||
content: '';
|
||||
background-color: #141414;
|
||||
margin-bottom: env(safe-area-inset-bottom, 0);
|
||||
}
|
||||
|
||||
.drawer-menu-container {
|
||||
height: 100vh;
|
||||
display: flex;
|
||||
align-items: flex-end;
|
||||
}
|
||||
|
||||
.drawer-menu {
|
||||
overflow-y: auto;
|
||||
margin-bottom: env(safe-area-inset-bottom, 0);
|
||||
width: 100%;
|
||||
|
||||
&#{&} .overflow-menu {
|
||||
margin: auto;
|
||||
font-size: 1.2em;
|
||||
list-style-type: none;
|
||||
padding: 0;
|
||||
height: calc(80vh - 144px - 64px);
|
||||
overflow-y: auto;
|
||||
|
||||
.overflow-menu-item {
|
||||
box-sizing: border-box;
|
||||
height: 48px;
|
||||
padding: 12px 16px;
|
||||
|
||||
align-items: center;
|
||||
color: #fff;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
font-size: 16px;
|
||||
|
||||
div {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
&.disabled {
|
||||
cursor: initial;
|
||||
color: #3b475c;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,42 +1,3 @@
|
||||
.participants_pane {
|
||||
background-color: #141414;
|
||||
flex-shrink: 0;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
transition: width .16s ease-in-out;
|
||||
width: 315px;
|
||||
z-index: $zindex0;
|
||||
}
|
||||
|
||||
.participants_pane-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
font-weight: 600;
|
||||
height: 100%;
|
||||
width: 315px;
|
||||
|
||||
& > *:first-child,
|
||||
& > *:last-child {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 580px) {
|
||||
.participants_pane {
|
||||
height: 100vh;
|
||||
height: -webkit-fill-available;
|
||||
left: 0;
|
||||
position: fixed;
|
||||
right: 0;
|
||||
top: 0;
|
||||
width: auto;
|
||||
}
|
||||
|
||||
.participants_pane-content {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.jitsi-icon {
|
||||
&-dominant-speaker {
|
||||
background-color: #1EC26A;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -73,7 +73,6 @@ $flagsImagePath: "../images/";
|
||||
@import 'modals/invite/invite_more';
|
||||
@import 'modals/security/security';
|
||||
@import 'responsive';
|
||||
@import 'drawer';
|
||||
@import 'participants-pane';
|
||||
@import 'reactions-menu';
|
||||
@import 'plan-limit';
|
||||
|
||||
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
|
||||
|
||||
160
ios/Podfile.lock
160
ios/Podfile.lock
@@ -3,11 +3,11 @@ PODS:
|
||||
- amplitude-react-native (2.7.0):
|
||||
- Amplitude (= 8.7.1)
|
||||
- React-Core
|
||||
- AppAuth (1.6.1):
|
||||
- AppAuth/Core (= 1.6.1)
|
||||
- AppAuth/ExternalUserAgent (= 1.6.1)
|
||||
- AppAuth/Core (1.6.1)
|
||||
- AppAuth/ExternalUserAgent (1.6.1):
|
||||
- AppAuth (1.6.2):
|
||||
- AppAuth/Core (= 1.6.2)
|
||||
- AppAuth/ExternalUserAgent (= 1.6.2)
|
||||
- AppAuth/Core (1.6.2)
|
||||
- AppAuth/ExternalUserAgent (1.6.2):
|
||||
- AppAuth/Core
|
||||
- boost (1.76.0)
|
||||
- CocoaLumberjack (3.7.2):
|
||||
@@ -103,56 +103,59 @@ PODS:
|
||||
- GoogleUtilities/Network (~> 7.7)
|
||||
- "GoogleUtilities/NSData+zlib (~> 7.7)"
|
||||
- nanopb (~> 2.30908.0)
|
||||
- GoogleDataTransport (9.2.2):
|
||||
- GoogleDataTransport (9.2.5):
|
||||
- GoogleUtilities/Environment (~> 7.7)
|
||||
- nanopb (< 2.30910.0, >= 2.30908.0)
|
||||
- PromisesObjC (< 3.0, >= 1.2)
|
||||
- GoogleSignIn (6.2.4):
|
||||
- GoogleSignIn (7.0.0):
|
||||
- AppAuth (~> 1.5)
|
||||
- GTMAppAuth (~> 1.3)
|
||||
- GTMSessionFetcher/Core (< 3.0, >= 1.1)
|
||||
- GoogleUtilities/AppDelegateSwizzler (7.11.1):
|
||||
- GTMAppAuth (< 3.0, >= 1.3)
|
||||
- GTMSessionFetcher/Core (< 4.0, >= 1.1)
|
||||
- GoogleUtilities/AppDelegateSwizzler (7.11.5):
|
||||
- GoogleUtilities/Environment
|
||||
- GoogleUtilities/Logger
|
||||
- GoogleUtilities/Network
|
||||
- GoogleUtilities/Environment (7.11.1):
|
||||
- GoogleUtilities/Environment (7.11.5):
|
||||
- PromisesObjC (< 3.0, >= 1.2)
|
||||
- GoogleUtilities/Logger (7.11.1):
|
||||
- GoogleUtilities/Logger (7.11.5):
|
||||
- GoogleUtilities/Environment
|
||||
- GoogleUtilities/MethodSwizzler (7.11.1):
|
||||
- GoogleUtilities/MethodSwizzler (7.11.5):
|
||||
- GoogleUtilities/Logger
|
||||
- GoogleUtilities/Network (7.11.1):
|
||||
- GoogleUtilities/Network (7.11.5):
|
||||
- GoogleUtilities/Logger
|
||||
- "GoogleUtilities/NSData+zlib"
|
||||
- GoogleUtilities/Reachability
|
||||
- "GoogleUtilities/NSData+zlib (7.11.1)"
|
||||
- GoogleUtilities/Reachability (7.11.1):
|
||||
- "GoogleUtilities/NSData+zlib (7.11.5)"
|
||||
- GoogleUtilities/Reachability (7.11.5):
|
||||
- GoogleUtilities/Logger
|
||||
- GoogleUtilities/UserDefaults (7.11.1):
|
||||
- GoogleUtilities/UserDefaults (7.11.5):
|
||||
- GoogleUtilities/Logger
|
||||
- GTMAppAuth (1.3.1):
|
||||
- GTMAppAuth (2.0.0):
|
||||
- AppAuth/Core (~> 1.6)
|
||||
- GTMSessionFetcher/Core (< 3.0, >= 1.5)
|
||||
- GTMSessionFetcher/Core (2.3.0)
|
||||
- GTMSessionFetcher/Core (< 4.0, >= 1.5)
|
||||
- GTMSessionFetcher/Core (3.1.1)
|
||||
- JitsiWebRTC (111.0.2)
|
||||
- libwebp (1.2.4):
|
||||
- libwebp/demux (= 1.2.4)
|
||||
- libwebp/mux (= 1.2.4)
|
||||
- libwebp/webp (= 1.2.4)
|
||||
- libwebp/demux (1.2.4):
|
||||
- libwebp (1.3.1):
|
||||
- libwebp/demux (= 1.3.1)
|
||||
- libwebp/mux (= 1.3.1)
|
||||
- libwebp/sharpyuv (= 1.3.1)
|
||||
- libwebp/webp (= 1.3.1)
|
||||
- libwebp/demux (1.3.1):
|
||||
- libwebp/webp
|
||||
- libwebp/mux (1.2.4):
|
||||
- libwebp/mux (1.3.1):
|
||||
- libwebp/demux
|
||||
- libwebp/webp (1.2.4)
|
||||
- libwebp/sharpyuv (1.3.1)
|
||||
- libwebp/webp (1.3.1):
|
||||
- libwebp/sharpyuv
|
||||
- nanopb (2.30908.0):
|
||||
- nanopb/decode (= 2.30908.0)
|
||||
- nanopb/encode (= 2.30908.0)
|
||||
- nanopb/decode (2.30908.0)
|
||||
- nanopb/encode (2.30908.0)
|
||||
- ObjectiveDropboxOfficial (6.2.3)
|
||||
- PromisesObjC (2.2.0)
|
||||
- PromisesSwift (2.2.0):
|
||||
- PromisesObjC (= 2.2.0)
|
||||
- PromisesObjC (2.3.1)
|
||||
- PromisesSwift (2.3.1):
|
||||
- PromisesObjC (= 2.3.1)
|
||||
- RCT-Folly (2021.06.28.00-v2):
|
||||
- boost
|
||||
- DoubleConversion
|
||||
@@ -364,36 +367,34 @@ PODS:
|
||||
- glog
|
||||
- react-native-background-timer (2.4.1):
|
||||
- React-Core
|
||||
- react-native-get-random-values (1.7.2):
|
||||
- react-native-get-random-values (1.9.0):
|
||||
- React-Core
|
||||
- react-native-keep-awake (4.0.0):
|
||||
- React
|
||||
- react-native-netinfo (7.1.7):
|
||||
- react-native-netinfo (9.4.1):
|
||||
- React-Core
|
||||
- react-native-orientation-locker (1.5.0):
|
||||
- React-Core
|
||||
- react-native-pager-view (5.4.9):
|
||||
- react-native-pager-view (6.2.0):
|
||||
- React-Core
|
||||
- react-native-safe-area-context (4.6.4):
|
||||
- RCT-Folly
|
||||
- RCTRequired
|
||||
- RCTTypeSafety
|
||||
- react-native-performance (5.0.0):
|
||||
- React-Core
|
||||
- ReactCommon/turbomodule/core
|
||||
- react-native-slider (4.1.12):
|
||||
- react-native-safe-area-context (4.7.1):
|
||||
- React-Core
|
||||
- react-native-slider (4.4.3):
|
||||
- React-Core
|
||||
- react-native-splash-screen (3.3.0):
|
||||
- React-Core
|
||||
- react-native-video (6.0.0-alpha.1):
|
||||
- react-native-video (6.0.0-alpha.7):
|
||||
- React-Core
|
||||
- react-native-video/Video (= 6.0.0-alpha.1)
|
||||
- react-native-video/Video (6.0.0-alpha.1):
|
||||
- react-native-video/Video (= 6.0.0-alpha.7)
|
||||
- react-native-video/Video (6.0.0-alpha.7):
|
||||
- PromisesSwift
|
||||
- React-Core
|
||||
- react-native-webrtc (111.0.3):
|
||||
- JitsiWebRTC (~> 111.0.0)
|
||||
- React-Core
|
||||
- react-native-webview (11.15.1):
|
||||
- react-native-webview (13.5.1):
|
||||
- React-Core
|
||||
- React-perflogger (0.69.11)
|
||||
- React-RCTActionSheet (0.69.11):
|
||||
@@ -463,30 +464,30 @@ PODS:
|
||||
- React-perflogger (= 0.69.11)
|
||||
- RNCalendarEvents (2.2.0):
|
||||
- React
|
||||
- RNCAsyncStorage (1.17.3):
|
||||
- RNCAsyncStorage (1.19.3):
|
||||
- React-Core
|
||||
- RNCClipboard (1.5.1):
|
||||
- React-Core
|
||||
- RNDefaultPreference (1.4.4):
|
||||
- React-Core
|
||||
- RNDeviceInfo (8.4.8):
|
||||
- RNDeviceInfo (10.9.0):
|
||||
- React-Core
|
||||
- RNGestureHandler (2.9.0):
|
||||
- React-Core
|
||||
- RNGoogleSignin (9.0.2):
|
||||
- GoogleSignIn (~> 6.2)
|
||||
- RNGoogleSignin (10.0.1):
|
||||
- GoogleSignIn (~> 7.0)
|
||||
- React-Core
|
||||
- RNScreens (3.22.0):
|
||||
- RNScreens (3.24.0):
|
||||
- React-Core
|
||||
- React-RCTImage
|
||||
- RNSound (0.11.1):
|
||||
- RNSound (0.11.2):
|
||||
- React-Core
|
||||
- RNSound/Core (= 0.11.1)
|
||||
- RNSound/Core (0.11.1):
|
||||
- RNSound/Core (= 0.11.2)
|
||||
- RNSound/Core (0.11.2):
|
||||
- React-Core
|
||||
- RNSVG (12.4.3):
|
||||
- RNSVG (13.13.0):
|
||||
- React-Core
|
||||
- RNWatch (1.0.11):
|
||||
- RNWatch (1.1.0):
|
||||
- React
|
||||
- Yoga (1.14.0)
|
||||
|
||||
@@ -502,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`)
|
||||
@@ -525,6 +525,7 @@ DEPENDENCIES:
|
||||
- "react-native-netinfo (from `../node_modules/@react-native-community/netinfo`)"
|
||||
- react-native-orientation-locker (from `../node_modules/react-native-orientation-locker`)
|
||||
- react-native-pager-view (from `../node_modules/react-native-pager-view`)
|
||||
- react-native-performance (from `../node_modules/react-native-performance`)
|
||||
- react-native-safe-area-context (from `../node_modules/react-native-safe-area-context`)
|
||||
- "react-native-slider (from `../node_modules/@react-native-community/slider`)"
|
||||
- react-native-splash-screen (from `../node_modules/react-native-splash-screen`)
|
||||
@@ -638,6 +639,8 @@ EXTERNAL SOURCES:
|
||||
:path: "../node_modules/react-native-orientation-locker"
|
||||
react-native-pager-view:
|
||||
:path: "../node_modules/react-native-pager-view"
|
||||
react-native-performance:
|
||||
:path: "../node_modules/react-native-performance"
|
||||
react-native-safe-area-context:
|
||||
:path: "../node_modules/react-native-safe-area-context"
|
||||
react-native-slider:
|
||||
@@ -702,7 +705,7 @@ EXTERNAL SOURCES:
|
||||
SPEC CHECKSUMS:
|
||||
Amplitude: 834c7332dfb9640a751e21c13efb22a07c0c12d4
|
||||
amplitude-react-native: 0ed8cab759aafaa94961b82122bf56297da607ad
|
||||
AppAuth: e48b432bb4ba88b10cb2bcc50d7f3af21e78b9c2
|
||||
AppAuth: 3bb1d1cd9340bd09f5ed189fb00b1cc28e1e8570
|
||||
boost: a7c83b31436843459a1961bfd74b96033dc77234
|
||||
CocoaLumberjack: b7e05132ff94f6ae4dfa9d5bce9141893a21d9da
|
||||
DoubleConversion: 5189b271737e1565bdce30deb4a08d647e3f5f54
|
||||
@@ -720,17 +723,17 @@ SPEC CHECKSUMS:
|
||||
giphy-react-native-sdk: fcda9639f8ca2cc47e0517b6ef11c19359db5f5a
|
||||
glog: 3d02b25ca00c2d456734d0bcff864cbc62f6ae1a
|
||||
GoogleAppMeasurement: 4c19f031220c72464d460c9daa1fb5d1acce958e
|
||||
GoogleDataTransport: 8378d1fa8ac49753ea6ce70d65a7cb70ce5f66e6
|
||||
GoogleSignIn: 5651ce3a61e56ca864160e79b484cd9ed3f49b7a
|
||||
GoogleUtilities: 9aa0ad5a7bc171f8bae016300bfcfa3fb8425749
|
||||
GTMAppAuth: 0ff230db599948a9ad7470ca667337803b3fc4dd
|
||||
GTMSessionFetcher: 3a63d75eecd6aa32c2fc79f578064e1214dfdec2
|
||||
GoogleDataTransport: 54dee9d48d14580407f8f5fbf2f496e92437a2f2
|
||||
GoogleSignIn: b232380cf495a429b8095d3178a8d5855b42e842
|
||||
GoogleUtilities: 13e2c67ede716b8741c7989e26893d151b2b2084
|
||||
GTMAppAuth: 99fb010047ba3973b7026e45393f51f27ab965ae
|
||||
GTMSessionFetcher: e8647203b65cee28c5f73d0f473d096653945e72
|
||||
JitsiWebRTC: 80f62908fcf2a1160e0d14b584323fb6e6be630b
|
||||
libwebp: f62cb61d0a484ba548448a4bd52aabf150ff6eef
|
||||
libwebp: 33dc822fbbf4503668d09f7885bbfedc76c45e96
|
||||
nanopb: a0ba3315591a9ae0a16a309ee504766e90db0c96
|
||||
ObjectiveDropboxOfficial: fe206ce8c0bc49976c249d472db7fdbc53ebbd53
|
||||
PromisesObjC: 09985d6d70fbe7878040aa746d78236e6946d2ef
|
||||
PromisesSwift: cf9eb58666a43bbe007302226e510b16c1e10959
|
||||
PromisesObjC: c50d2056b5253dadbd6c2bea79b0674bd5a52fa4
|
||||
PromisesSwift: 28dca69a9c40779916ac2d6985a0192a5cb4a265
|
||||
RCT-Folly: b9d9fe1fc70114b751c076104e52f3b1b5e5a95a
|
||||
RCTRequired: 8e9a57dddc8f8e9e816c67c2d2537271a997137a
|
||||
RCTTypeSafety: 2b19e268e2036a2c2f6db6deb1ac03e28b1d607a
|
||||
@@ -746,17 +749,18 @@ SPEC CHECKSUMS:
|
||||
React-jsinspector: 9ca5bf73ed0a195397e45fdbcd507cf7d503c428
|
||||
React-logger: 700340e325f21ba2a2d6413a61ef14268c7360aa
|
||||
react-native-background-timer: 17ea5e06803401a379ebf1f20505b793ac44d0fe
|
||||
react-native-get-random-values: 30b3f74ca34e30e2e480de48e4add2706a40ac8f
|
||||
react-native-get-random-values: dee677497c6a740b71e5612e8dbd83e7539ed5bb
|
||||
react-native-keep-awake: afad8a51dfef9fe9655a6344771be32c8596d774
|
||||
react-native-netinfo: 27f287f2d191693f3b9d01a4273137fcf91c3b5d
|
||||
react-native-netinfo: fefd4e98d75cbdd6e85fc530f7111a8afdf2b0c5
|
||||
react-native-orientation-locker: 851f6510d8046ea2f14aa169b1e01fcd309a94ba
|
||||
react-native-pager-view: 3ee7d4c7697fb3ef788346e834a60cca97ed8540
|
||||
react-native-safe-area-context: 68b07eabfb0d14547d36f6929c0e98d818064f02
|
||||
react-native-slider: 6e9b86e76cce4b9e35b3403193a6432ed07e0c81
|
||||
react-native-pager-view: 0ccb8bf60e2ebd38b1f3669fa3650ecce81db2df
|
||||
react-native-performance: 47ac22ebf2aa24f324a96a5825581f6ce18c09e8
|
||||
react-native-safe-area-context: 9697629f7b2cda43cf52169bb7e0767d330648c2
|
||||
react-native-slider: 1cdd6ba29675df21f30544253bf7351d3c2d68c4
|
||||
react-native-splash-screen: 4312f786b13a81b5169ef346d76d33bc0c6dc457
|
||||
react-native-video: bb6f12a7198db53b261fefb5d609dc77417acc8b
|
||||
react-native-video: 967eead48aaa42c25a9e1d65c3b1ab30762a88df
|
||||
react-native-webrtc: 4d1669c2ed29767fe70b0169428b4466589ecf8b
|
||||
react-native-webview: ea4899a1056c782afa96dd082179a66cbebf5504
|
||||
react-native-webview: 8baa0f5c6d336d6ba488e942bcadea5bf51f050a
|
||||
React-perflogger: fdee2a0c512167ae4c19c4e230ccf6aa66a6aff0
|
||||
React-RCTActionSheet: 1cf5fef4e372f1c877969710a51bea4bb25e78fe
|
||||
React-RCTAnimation: 73816e3acd1f5e3f00166fc7eedb34f6b112f734
|
||||
@@ -770,18 +774,18 @@ SPEC CHECKSUMS:
|
||||
React-runtimeexecutor: 59407514818b2afbb1d7507e4e1ac834d24b0fbd
|
||||
ReactCommon: b8487da74723562d7368dab27135fd182f00a91c
|
||||
RNCalendarEvents: 7e65eb4a94f53c1744d1e275f7fafcfaa619f7a3
|
||||
RNCAsyncStorage: 005c0e2f09575360f142d0d1f1f15e4ec575b1af
|
||||
RNCAsyncStorage: c913ede1fa163a71cea118ed4670bbaaa4b511bb
|
||||
RNCClipboard: 41d8d918092ae8e676f18adada19104fa3e68495
|
||||
RNDefaultPreference: 08bdb06cfa9188d5da97d4642dac745218d7fb31
|
||||
RNDeviceInfo: 0400a6d0c94186d1120c3cbd97b23abc022187a9
|
||||
RNDeviceInfo: 02ea8b23e2280fa18e00a06d7e62804d74028579
|
||||
RNGestureHandler: 071d7a9ad81e8b83fe7663b303d132406a7d8f39
|
||||
RNGoogleSignin: 22e468a9474dbcb8618d8847205ad4f0b2575d13
|
||||
RNScreens: 68fd1060f57dd1023880bf4c05d74784b5392789
|
||||
RNSound: 27e8268bdb0a1f191f219a33267f7e0445e8d62f
|
||||
RNSVG: f3b60aeeaa81960e2e0536c3a9eef50b667ef3a9
|
||||
RNWatch: dae6c858a2051dbdcfb00b9a86cf4d90400263b4
|
||||
RNGoogleSignin: ccaa4a81582cf713eea562c5dd9dc1961a715fd0
|
||||
RNScreens: b21dc57dfa2b710c30ec600786a3fc223b1b92e7
|
||||
RNSound: 6c156f925295bdc83e8e422e7d8b38d33bc71852
|
||||
RNSVG: ed492aaf3af9ca01bc945f7a149d76d62e73ec82
|
||||
RNWatch: fd30ca40a5b5ef58dcbc195638e68219bc455236
|
||||
Yoga: 7f5ad94937ba3fc58c151ad1b7bbada2c275b28e
|
||||
|
||||
PODFILE CHECKSUM: e3579df5272b8b697c9fdc0e55aa0845b189c4dd
|
||||
PODFILE CHECKSUM: 90720aee51cf2cab2e12611a28dbf55a688e969c
|
||||
|
||||
COCOAPODS: 1.12.1
|
||||
|
||||
@@ -250,13 +250,14 @@
|
||||
"dialog": {
|
||||
"Back": "Voltar",
|
||||
"Cancel": "Cancelar",
|
||||
"IamHost": "Eu sou o anfitrião",
|
||||
"IamHost": "Iniciar sessão",
|
||||
"Ok": "OK",
|
||||
"Remove": "Remover",
|
||||
"Share": "Partilhar",
|
||||
"Submit": "Submeter",
|
||||
"WaitForHostMsg": "A conferência ainda não começou. Se for o anfitrião, por favor autentique. Caso contrário, por favor aguarde que o anfitrião chegue.",
|
||||
"WaitingForHostTitle": "À espera do anfitrião ...",
|
||||
"WaitForHostMsg": "A conferência ainda não começou porque ainda não chegaram moderadores. Se quiser ser um moderador, inicie a sessão. Caso contrário, aguarde.",
|
||||
"WaitingForHostButton": "Esperar pelo moderador",
|
||||
"WaitingForHostTitle": "À espera de um moderador...",
|
||||
"Yes": "Sim",
|
||||
"accessibilityLabel": {
|
||||
"Cancel": "Cancelar (sair da caixa de diálogo)",
|
||||
@@ -269,6 +270,8 @@
|
||||
"addMeetingNote": "Acrescentar uma nota sobre esta reunião",
|
||||
"addOptionalNote": "Adicionar uma nota (opcional):",
|
||||
"allow": "Permitir",
|
||||
"allowToggleCameraDialog": "Permite que {{initiatorName}} alterne o modo de visualização da câmara?",
|
||||
"allowToggleCameraTitle": "Permitir alternar a câmara?",
|
||||
"alreadySharedVideoMsg": "Outro participante já está a partilhar um vídeo. Esta conferência permite apenas um vídeo partilhado de cada vez.",
|
||||
"alreadySharedVideoTitle": "Só é permitido um vídeo partilhado de cada vez",
|
||||
"applicationWindow": "Janela de aplicação",
|
||||
@@ -329,6 +332,7 @@
|
||||
"lockRoom": "Adicionar reunião $t(lockRoomPassword)",
|
||||
"lockTitle": "Bloqueio falhado",
|
||||
"login": "Entrar",
|
||||
"loginQuestion": "Tem a certeza de que pretende iniciar sessão e abandonar a conferência?",
|
||||
"logoutQuestion": "Tem a certeza de que quer terminar a sessão e sair da conferência?",
|
||||
"logoutTitle": "Sair",
|
||||
"maxUsersLimitReached": "O limite para o número máximo de participantes foi atingido. A conferência está cheia. Por favor contacte o proprietário da reunião ou tente novamente mais tarde!",
|
||||
@@ -409,6 +413,7 @@
|
||||
"sendPrivateMessageTitle": "Enviar em privado?",
|
||||
"serviceUnavailable": "Serviço indisponível",
|
||||
"sessTerminated": "Chamada terminada",
|
||||
"sessTerminatedReason": "A reunião foi encerrada",
|
||||
"sessionRestarted": "Chamada reiniciada devido a um problema de ligação.",
|
||||
"shareAudio": "Continuar",
|
||||
"shareAudioTitle": "Como partilhar áudio",
|
||||
@@ -440,7 +445,24 @@
|
||||
"thankYou": "Obrigado por utilizar {{appName}}!",
|
||||
"token": "token",
|
||||
"tokenAuthFailed": "Desculpe, não está autorizado a juntar-se a esta chamada.",
|
||||
"tokenAuthFailedReason": {
|
||||
"audInvalid": "Valor `aud` inválido. Deveria ser `jitsi`.",
|
||||
"contextNotFound": "O objeto `context` está em falta na carga útil.",
|
||||
"expInvalid": "Valor `exp` inválido.",
|
||||
"featureInvalid": "Funcionalidade inválida: {{feature}}, muito provavelmente ainda não implementada.",
|
||||
"featureValueInvalid": "Valor inválido para a caraterística: {{feature}}.",
|
||||
"featuresNotFound": "O objeto `features` está em falta na carga útil.",
|
||||
"headerNotFound": "Falta o cabeçalho.",
|
||||
"issInvalid": "Valor `iss` inválido. Deveria ser `chat`.",
|
||||
"kidMismatch": "O ID da chave (kid) não corresponde ao sub.",
|
||||
"kidNotFound": "Falta o ID da chave (kid)",
|
||||
"nbfFuture": "O valor `nbf` está no futuro.",
|
||||
"nbfInvalid": "Valor `nbf` inválido.",
|
||||
"payloadNotFound": "Falta a carga útil.",
|
||||
"tokenExpired": "O token expirou."
|
||||
},
|
||||
"tokenAuthFailedTitle": "A autenticação falhou",
|
||||
"tokenAuthFailedWithReasons": "Lamentamos, mas não está autorizado a participar nesta chamada. Razões possíveis: {{reason}}",
|
||||
"tokenAuthUnsupported": "O URL de token não é suportado.",
|
||||
"transcribing": "Transcrição",
|
||||
"unlockRoom": "Retirar reunião $t(lockRoomPassword)",
|
||||
@@ -529,7 +551,8 @@
|
||||
"numbers": "Números para entrar por chamada telefónica",
|
||||
"password": "$t(lockRoomPasswordUppercase): ",
|
||||
"reachedLimit": "atingiu o limite do seu plano.",
|
||||
"sip": "Endereços SIP",
|
||||
"sip": "Endereço SIP",
|
||||
"sipAudioOnly": "Endereço SIP só de áudio",
|
||||
"title": "Partilhar",
|
||||
"tooltip": "Partilhar link e acesso telefónico para esta reunião",
|
||||
"upgradeOptions": "Por favor, verifique as opções de atualização em"
|
||||
@@ -593,15 +616,15 @@
|
||||
"limitNotificationDescriptionWeb": "Devido à grande procura, a sua transmissão será limitada a {{limit}} min. Para uma tentativa de streaming ilimitada tente <a href={{url}} rel='noopener noreferrer' target='_blank'>{{app}}</a>.",
|
||||
"off": "Transmissão em direto encerrada",
|
||||
"offBy": "{{name}} parou a transmissão em direto",
|
||||
"on": "Transmissão em Direto",
|
||||
"on": "Iniciada a transmissão em direto",
|
||||
"onBy": "{{name}} iniciou a transmissão em direto",
|
||||
"pending": "Iniciando Transmissão em Direto...",
|
||||
"pending": "Início da transmissão em direto...",
|
||||
"serviceName": "Serviço de Transmissão em Direto",
|
||||
"sessionAlreadyActive": "Esta sessão já está a ser gravada ou transmitida em direto.",
|
||||
"signIn": "Faça login no Google",
|
||||
"signInCTA": "Faça login ou insira sua chave de transmissão em Direto do YouTube.",
|
||||
"signIn": "Iniciar sessão com o Google",
|
||||
"signInCTA": "Inicie sessão ou introduza a sua chave de transmissão em direto do YouTube.",
|
||||
"signOut": "Sair",
|
||||
"signedInAs": "Está conectado como:",
|
||||
"signedInAs": "Atualmente, tem sessão iniciada como:",
|
||||
"start": "Iniciar uma transmissão em direto",
|
||||
"streamIdHelp": "O que é isso?",
|
||||
"title": "Transmissão em direto",
|
||||
@@ -636,13 +659,13 @@
|
||||
"knockingParticipantList": "Lista de participantes a expulsar",
|
||||
"lobbyChatStartedNotification": "{{moderator}} iniciou com {{attendee}} uma conversa na sala de espera",
|
||||
"lobbyChatStartedTitle": "{{moderator}} iniciou consigo uma conversa na sala de espera.",
|
||||
"lobbyClosed": "A sala de espera foi encerrada.",
|
||||
"nameField": "Introduza o seu nome",
|
||||
"notificationLobbyAccessDenied": "{{targetParticipantName}} foi recusada a adesão por {{originParticipantName}}",
|
||||
"notificationLobbyAccessGranted": "{{targetParticipantName}} foi autorizado a aderir por {{originParticipantName}}",
|
||||
"notificationLobbyDisabled": "A sala de espera foi desactivada por {{originParticipantName}}",
|
||||
"notificationLobbyEnabled": "A sala de espera foi activada por {{originParticipantName}}",
|
||||
"notificationTitle": "Sala de espera",
|
||||
"passwordField": "Introduza a senha da reunião",
|
||||
"passwordJoinButton": "Solicitar",
|
||||
"reject": "Rejeitar",
|
||||
"rejectAll": "Rejeitar todos",
|
||||
@@ -1061,6 +1084,7 @@
|
||||
"alertOk": "OK",
|
||||
"alertTitle": "Atenção",
|
||||
"alertURLText": "A URL digitada do servidor é inválida",
|
||||
"apply": "Aplicar",
|
||||
"buildInfoSection": "Informações de compilação",
|
||||
"conferenceSection": "Conferência",
|
||||
"disableCallIntegration": "Desactivar a integração de chamadas nativas",
|
||||
@@ -1071,6 +1095,7 @@
|
||||
"displayNamePlaceholderText": "Ex: João Dias",
|
||||
"email": "Email",
|
||||
"emailPlaceholderText": "email@example.com",
|
||||
"gavatarMessage": "Se o seu e-mail estiver associado a uma conta Gravatar, utilizá-la-emos para apresentar a sua imagem de perfil.",
|
||||
"goTo": "Ir para",
|
||||
"header": "Configurações",
|
||||
"help": "Ajuda",
|
||||
@@ -1254,7 +1279,7 @@
|
||||
"lobbyButtonDisable": "Desativar sala de espera",
|
||||
"lobbyButtonEnable": "Ativar sala de espera",
|
||||
"login": "Iniciar sessão",
|
||||
"logout": "Encerrar sessão",
|
||||
"logout": "Terminar sessão",
|
||||
"lowerYourHand": "Baixar a mão",
|
||||
"moreActions": "Mais ações",
|
||||
"moreOptions": "Mais opções",
|
||||
|
||||
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
|
||||
34
modules/API/external/external_api.js
vendored
34
modules/API/external/external_api.js
vendored
@@ -305,6 +305,9 @@ export default class JitsiMeetExternalAPI extends EventEmitter {
|
||||
* configuration options defined in config.js to be overridden.
|
||||
* @param {Object} [options.interfaceConfigOverwrite] - Object containing
|
||||
* configuration options defined in interface_config.js to be overridden.
|
||||
* @param {IIceServers} [options.iceServers] - Object with rules that will be used to modify/remove the existing
|
||||
* ice server configuration.
|
||||
* NOTE: This property is currently experimental and may be removed in the future!
|
||||
* @param {string} [options.jwt] - The JWT token if needed by jitsi-meet for
|
||||
* authentication.
|
||||
* @param {string} [options.lang] - The meeting's default language.
|
||||
@@ -334,6 +337,7 @@ export default class JitsiMeetExternalAPI extends EventEmitter {
|
||||
lang = undefined,
|
||||
onload = undefined,
|
||||
invitees,
|
||||
iceServers,
|
||||
devices,
|
||||
userInfo,
|
||||
e2eeKey,
|
||||
@@ -345,6 +349,7 @@ export default class JitsiMeetExternalAPI extends EventEmitter {
|
||||
this._parentNode = parentNode;
|
||||
this._url = generateURL(domain, {
|
||||
configOverwrite,
|
||||
iceServers,
|
||||
interfaceConfigOverwrite,
|
||||
jwt,
|
||||
lang,
|
||||
@@ -398,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);
|
||||
@@ -1191,6 +1205,24 @@ export default class JitsiMeetExternalAPI extends EventEmitter {
|
||||
return this._numberOfParticipants;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns array of commands supported by executeCommand().
|
||||
*
|
||||
* @returns {Array<string>} Array of commands.
|
||||
*/
|
||||
getSupportedCommands() {
|
||||
return Object.keys(commands);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns array of events supported by addEventListener().
|
||||
*
|
||||
* @returns {Array<string>} Array of events.
|
||||
*/
|
||||
getSupportedEvents() {
|
||||
return Object.values(events);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the video is available.
|
||||
*
|
||||
|
||||
2367
package-lock.json
generated
2367
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
49
package.json
49
package.json
@@ -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",
|
||||
@@ -31,11 +31,11 @@
|
||||
"@microsoft/microsoft-graph-client": "3.0.1",
|
||||
"@mui/material": "5.12.1",
|
||||
"@mui/styles": "5.12.0",
|
||||
"@react-native-async-storage/async-storage": "1.17.3",
|
||||
"@react-native-async-storage/async-storage": "1.19.3",
|
||||
"@react-native-community/clipboard": "1.5.1",
|
||||
"@react-native-community/netinfo": "7.1.7",
|
||||
"@react-native-community/slider": "4.1.12",
|
||||
"@react-native-google-signin/google-signin": "9.0.2",
|
||||
"@react-native-community/netinfo": "9.4.1",
|
||||
"@react-native-community/slider": "4.4.3",
|
||||
"@react-native-google-signin/google-signin": "10.0.1",
|
||||
"@react-navigation/bottom-tabs": "6.5.8",
|
||||
"@react-navigation/elements": "1.3.18",
|
||||
"@react-navigation/material-top-tabs": "6.6.3",
|
||||
@@ -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/v1680.0.0+b760f4e1/lib-jitsi-meet.tgz",
|
||||
"lib-jitsi-meet": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v1694.0.0+a0145343/lib-jitsi-meet.tgz",
|
||||
"lodash": "4.17.21",
|
||||
"moment": "2.29.4",
|
||||
"moment-duration-format": "2.2.2",
|
||||
@@ -82,31 +82,31 @@
|
||||
"react-native": "0.69.11",
|
||||
"react-native-background-timer": "2.4.1",
|
||||
"react-native-calendar-events": "2.2.0",
|
||||
"react-native-callstats": "3.73.7",
|
||||
"react-native-callstats": "3.73.22",
|
||||
"react-native-default-preference": "1.4.4",
|
||||
"react-native-device-info": "8.4.8",
|
||||
"react-native-device-info": "10.9.0",
|
||||
"react-native-dialog": "https://github.com/jitsi/react-native-dialog/releases/download/v9.2.2-jitsi.1/react-native-dialog-9.2.2.tgz",
|
||||
"react-native-gesture-handler": "2.9.0",
|
||||
"react-native-get-random-values": "1.7.2",
|
||||
"react-native-get-random-values": "1.9.0",
|
||||
"react-native-immersive-mode": "2.0.1",
|
||||
"react-native-keep-awake": "4.0.0",
|
||||
"react-native-orientation-locker": "1.5.0",
|
||||
"react-native-pager-view": "5.4.9",
|
||||
"react-native-paper": "5.1.2",
|
||||
"react-native-performance": "2.1.0",
|
||||
"react-native-safe-area-context": "4.6.4",
|
||||
"react-native-screens": "3.22.0",
|
||||
"react-native-sound": "0.11.1",
|
||||
"react-native-pager-view": "6.2.0",
|
||||
"react-native-paper": "5.10.3",
|
||||
"react-native-performance": "5.0.0",
|
||||
"react-native-safe-area-context": "4.7.1",
|
||||
"react-native-screens": "3.24.0",
|
||||
"react-native-sound": "0.11.2",
|
||||
"react-native-splash-screen": "3.3.0",
|
||||
"react-native-svg": "12.4.3",
|
||||
"react-native-svg-transformer": "1.0.0",
|
||||
"react-native-tab-view": "3.1.1",
|
||||
"react-native-url-polyfill": "1.3.0",
|
||||
"react-native-video": "https://git@github.com/react-native-video/react-native-video#7c48ae7c8544b2b537fb60194e9620b9fcceae52",
|
||||
"react-native-watch-connectivity": "1.0.11",
|
||||
"react-native-svg": "13.13.0",
|
||||
"react-native-svg-transformer": "1.1.0",
|
||||
"react-native-tab-view": "3.5.2",
|
||||
"react-native-url-polyfill": "2.0.0",
|
||||
"react-native-video": "6.0.0-alpha.7",
|
||||
"react-native-watch-connectivity": "1.1.0",
|
||||
"react-native-webrtc": "111.0.3",
|
||||
"react-native-webview": "11.15.1",
|
||||
"react-native-youtube-iframe": "2.2.1",
|
||||
"react-native-webview": "13.5.1",
|
||||
"react-native-youtube-iframe": "2.3.0",
|
||||
"react-redux": "7.2.9",
|
||||
"react-textarea-autosize": "8.3.0",
|
||||
"react-window": "1.8.6",
|
||||
@@ -115,6 +115,7 @@
|
||||
"redux-thunk": "2.4.1",
|
||||
"resemblejs": "4.0.0",
|
||||
"seamless-scroll-polyfill": "2.1.8",
|
||||
"semver": "7.5.4",
|
||||
"tss-react": "4.4.4",
|
||||
"util": "0.12.1",
|
||||
"uuid": "8.3.2",
|
||||
|
||||
1426
react-native-sdk/package-lock.json
generated
1426
react-native-sdk/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -11,9 +11,8 @@
|
||||
"url": "git+https://github.com/jitsi/jitsi-meet.git"
|
||||
},
|
||||
"dependencies": {
|
||||
"@hapi/bourne": "2.0.0",
|
||||
"@jitsi/js-utils": "2.0.5",
|
||||
"@jitsi/logger": "2.0.0",
|
||||
"@jitsi/js-utils": "2.1.3",
|
||||
"@jitsi/logger": "2.0.2",
|
||||
"@jitsi/rtcstats": "9.5.1",
|
||||
"@react-navigation/bottom-tabs": "6.5.8",
|
||||
"@react-navigation/elements": "1.3.18",
|
||||
@@ -25,11 +24,11 @@
|
||||
"grapheme-splitter": "1.0.4",
|
||||
"i18n-iso-countries": "6.8.0",
|
||||
"i18next": "17.0.6",
|
||||
"i18next-xhr-backend": "3.0.0",
|
||||
"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/v1670.0.0+10ebc843/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",
|
||||
@@ -39,12 +38,12 @@
|
||||
"react-emoji-render": "1.2.4",
|
||||
"react-i18next": "10.11.4",
|
||||
"react-linkify": "1.0.0-alpha",
|
||||
"react-native-callstats": "3.73.7",
|
||||
"react-native-callstats": "3.73.22",
|
||||
"react-native-dialog": "https://github.com/jitsi/react-native-dialog/releases/download/v9.2.2-jitsi.1/react-native-dialog-9.2.2.tgz",
|
||||
"react-native-svg-transformer": "1.0.0",
|
||||
"react-native-tab-view": "3.1.1",
|
||||
"react-native-url-polyfill": "1.3.0",
|
||||
"react-native-youtube-iframe": "2.2.1",
|
||||
"react-native-svg-transformer": "1.1.0",
|
||||
"react-native-tab-view": "3.5.2",
|
||||
"react-native-url-polyfill": "2.0.0",
|
||||
"react-native-youtube-iframe": "2.3.0",
|
||||
"react-redux": "7.2.9",
|
||||
"redux": "4.0.4",
|
||||
"redux-thunk": "2.4.1",
|
||||
@@ -56,34 +55,34 @@
|
||||
"peerDependencies": {
|
||||
"@amplitude/react-native": "2.7.0",
|
||||
"@giphy/react-native-sdk": "2.3.0",
|
||||
"@react-native-async-storage/async-storage": "1.18.2",
|
||||
"@react-native-async-storage/async-storage": "1.19.3",
|
||||
"@react-native-community/clipboard": "1.5.1",
|
||||
"@react-native-community/netinfo": "7.1.7",
|
||||
"@react-native-community/slider": "4.1.12",
|
||||
"@react-native-google-signin/google-signin": "7.0.4",
|
||||
"@react-native-community/netinfo": "9.4.1",
|
||||
"@react-native-community/slider": "4.4.3",
|
||||
"@react-native-google-signin/google-signin": "10.0.1",
|
||||
"react-native": "*",
|
||||
"react": "*",
|
||||
"react-native-background-timer": "2.4.1",
|
||||
"react-native-calendar-events": "2.2.0",
|
||||
"react-native-default-preference": "1.4.4",
|
||||
"react-native-device-info": "8.4.8",
|
||||
"react-native-get-random-values": "1.7.2",
|
||||
"react-native-device-info": "10.9.0",
|
||||
"react-native-get-random-values": "1.9.0",
|
||||
"react-native-gesture-handler": "2.9.0",
|
||||
"react-native-immersive-mode": "2.0.1",
|
||||
"react-native-keep-awake": "4.0.0",
|
||||
"react-native-pager-view": "5.4.9",
|
||||
"react-native-paper": "4.11.1",
|
||||
"react-native-performance": "2.1.0",
|
||||
"react-native-orientation-locker": "https://git@github.com/wonday/react-native-orientation-locker#f483520ea6b64b97002374a9e9f053a5299a062a",
|
||||
"react-native-safe-area-context": "4.4.1",
|
||||
"react-native-screens": "3.22.0",
|
||||
"react-native-sound": "0.11.1",
|
||||
"react-native-pager-view": "6.2.0",
|
||||
"react-native-paper": "5.10.3",
|
||||
"react-native-performance": "5.0.0",
|
||||
"react-native-orientation-locker": "1.5.0",
|
||||
"react-native-safe-area-context": "4.7.1",
|
||||
"react-native-screens": "3.24.0",
|
||||
"react-native-sound": "0.11.2",
|
||||
"react-native-splash-screen": "3.3.0",
|
||||
"react-native-svg": "12.4.3",
|
||||
"react-native-video": "https://git@github.com/react-native-video/react-native-video#7c48ae7c8544b2b537fb60194e9620b9fcceae52",
|
||||
"react-native-watch-connectivity": "1.0.11",
|
||||
"react-native-webrtc": "111.0.1",
|
||||
"react-native-webview": "11.15.1"
|
||||
"react-native-svg": "13.13.0",
|
||||
"react-native-video": "6.0.0-alpha.7",
|
||||
"react-native-watch-connectivity": "1.1.0",
|
||||
"react-native-webrtc": "111.0.3",
|
||||
"react-native-webview": "13.5.1"
|
||||
},
|
||||
"overrides": {
|
||||
"@xmldom/xmldom": "0.8.7"
|
||||
@@ -98,4 +97,4 @@
|
||||
"keywords": [
|
||||
"react-native"
|
||||
]
|
||||
}
|
||||
}
|
||||
14
react-native-sdk/prepare_sdk.js
vendored
14
react-native-sdk/prepare_sdk.js
vendored
@@ -58,11 +58,25 @@ function copyFolderRecursiveSync(source, target) {
|
||||
* Merges the dependency versions from the root package.json with the dependencies of the SDK package.json.
|
||||
*/
|
||||
function mergeDependencyVersions() {
|
||||
|
||||
// Updates SDK dependencies to match project dependencies.
|
||||
for (const key in SDKPackageJSON.dependencies) {
|
||||
if (SDKPackageJSON.dependencies.hasOwnProperty(key)) {
|
||||
SDKPackageJSON.dependencies[key] = packageJSON.dependencies[key] || packageJSON.devDependencies[key];
|
||||
}
|
||||
}
|
||||
|
||||
// Updates SDK peer dependencies.
|
||||
for (const key in packageJSON.dependencies) {
|
||||
if (SDKPackageJSON.peerDependencies.hasOwnProperty(key)) {
|
||||
|
||||
// Updates all peer dependencies except react and react-native.
|
||||
if (key !== 'react' && key !== 'react-native') {
|
||||
SDKPackageJSON.peerDependencies[key] = packageJSON.dependencies[key];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const data = JSON.stringify(SDKPackageJSON, null, 4);
|
||||
|
||||
fs.writeFileSync('package.json', data);
|
||||
|
||||
29
react-native-sdk/update_dependencies.js
vendored
29
react-native-sdk/update_dependencies.js
vendored
@@ -1,8 +1,10 @@
|
||||
/* eslint-disable guard-for-in */
|
||||
/* eslint-disable guard-for-in, no-continue */
|
||||
/* global __dirname */
|
||||
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const semver = require('semver');
|
||||
|
||||
|
||||
const pathToPackageJSON = path.resolve(__dirname, '../../../package.json');
|
||||
|
||||
@@ -10,6 +12,7 @@ const packageJSON = require(pathToPackageJSON);
|
||||
|
||||
const RNSDKpackageJSON = require(path.resolve(__dirname, './package.json'));
|
||||
|
||||
|
||||
/**
|
||||
* Updates dependencies from the app package.json with the peer dependencies of the RNSDK package.json.
|
||||
*/
|
||||
@@ -21,6 +24,28 @@ function updateDependencies() {
|
||||
packageJSON.dependencies[key] = RNSDKpackageJSON.peerDependencies[key];
|
||||
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;
|
||||
}
|
||||
|
||||
if (semver.satisfies(RNSDKpackageJSON.peerDependencies[key], `>${packageJSON.dependencies[key]}`)) {
|
||||
packageJSON.dependencies[key] = RNSDKpackageJSON.peerDependencies[key];
|
||||
updated = true;
|
||||
|
||||
console.log(`${key} is now set to ${RNSDKpackageJSON.peerDependencies[key]}`);
|
||||
}
|
||||
}
|
||||
|
||||
packageJSON.overrides = packageJSON.overrides || {};
|
||||
@@ -33,6 +58,8 @@ function updateDependencies() {
|
||||
}
|
||||
|
||||
if (!updated) {
|
||||
console.log('All your dependencies are up to date!');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -82,6 +82,7 @@ export async function createHandlers({ getState }: IStore) {
|
||||
} = config;
|
||||
const {
|
||||
amplitudeAPPKey,
|
||||
amplitudeIncludeUTM,
|
||||
blackListedEvents,
|
||||
scriptURLs,
|
||||
googleAnalyticsTrackingId,
|
||||
@@ -92,6 +93,7 @@ export async function createHandlers({ getState }: IStore) {
|
||||
const { group, user } = state['features/base/jwt'];
|
||||
const handlerConstructorOptions = {
|
||||
amplitudeAPPKey,
|
||||
amplitudeIncludeUTM,
|
||||
blackListedEvents,
|
||||
envType: deploymentInfo?.envType || 'dev',
|
||||
googleAnalyticsTrackingId,
|
||||
@@ -222,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 => {
|
||||
|
||||
@@ -11,6 +11,7 @@ export interface IEvent {
|
||||
|
||||
interface IOptions {
|
||||
amplitudeAPPKey?: string;
|
||||
amplitudeIncludeUTM?: boolean;
|
||||
blackListedEvents?: string[];
|
||||
envType?: string;
|
||||
googleAnalyticsTrackingId?: string;
|
||||
|
||||
@@ -15,13 +15,18 @@ export default class AmplitudeHandler extends AbstractHandler {
|
||||
* Creates new instance of the Amplitude analytics handler.
|
||||
*
|
||||
* @param {Object} options -
|
||||
* @param {string} options.amplitudeAPPKey - The Amplitude app key required
|
||||
* by the Amplitude API.
|
||||
* @param {string} options.amplitudeAPPKey - The Amplitude app key required by the Amplitude API.
|
||||
* @param {boolean} options.amplitudeIncludeUTM - Whether to include UTM parameters
|
||||
* in the Amplitude events.
|
||||
*/
|
||||
constructor(options: any) {
|
||||
super(options);
|
||||
|
||||
const { amplitudeAPPKey, user } = options;
|
||||
const {
|
||||
amplitudeAPPKey,
|
||||
amplitudeIncludeUTM: includeUtm = true,
|
||||
user
|
||||
} = options;
|
||||
|
||||
this._enabled = true;
|
||||
|
||||
@@ -43,6 +48,8 @@ export default class AmplitudeHandler extends AbstractHandler {
|
||||
} else {
|
||||
const amplitudeOptions: any = {
|
||||
includeReferrer: true,
|
||||
includeUtm,
|
||||
saveParamsReferrerOncePerSession: false,
|
||||
onError
|
||||
};
|
||||
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
import { Amplitude } from '@amplitude/react-native';
|
||||
import DefaultPreference from 'react-native-default-preference';
|
||||
import DeviceInfo from 'react-native-device-info';
|
||||
import { getUniqueId } from 'react-native-device-info';
|
||||
|
||||
import logger from '../../logger';
|
||||
|
||||
|
||||
/**
|
||||
* Custom logic for setting the correct device id.
|
||||
@@ -14,11 +17,17 @@ export async function fixDeviceID(amplitude: Amplitude) {
|
||||
const current = await DefaultPreference.get('amplitudeDeviceId');
|
||||
|
||||
if (current) {
|
||||
amplitude.setDeviceId(current);
|
||||
await amplitude.setDeviceId(current);
|
||||
} else {
|
||||
const uid = DeviceInfo.getUniqueId();
|
||||
const uid = await getUniqueId();
|
||||
|
||||
amplitude.setDeviceId(uid);
|
||||
DefaultPreference.set('amplitudeDeviceId', uid);
|
||||
if (!uid) {
|
||||
logger.warn('Device ID is not set!');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
await amplitude.setDeviceId(uid as string);
|
||||
await DefaultPreference.set('amplitudeDeviceId', uid as string);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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';
|
||||
@@ -33,6 +35,7 @@ import { addTrackStateToURL, getDefaultURL } from './functions.native';
|
||||
import logger from './logger';
|
||||
import { IReloadNowOptions, IStore } from './types';
|
||||
|
||||
|
||||
export * from './actions.any';
|
||||
|
||||
/**
|
||||
@@ -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());
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -27,6 +27,7 @@ import '../connection-indicator/middleware';
|
||||
import '../deep-linking/middleware';
|
||||
import '../device-selection/middleware';
|
||||
import '../display-name/middleware';
|
||||
import '../dynamic-branding/middleware';
|
||||
import '../etherpad/middleware';
|
||||
import '../filmstrip/middleware';
|
||||
import '../follow-me/middleware';
|
||||
|
||||
@@ -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));
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -2,9 +2,11 @@ import { createStartMutedConfigurationEvent } from '../../analytics/AnalyticsEve
|
||||
import { sendAnalytics } from '../../analytics/functions';
|
||||
import { IReduxState, IStore } from '../../app/types';
|
||||
import { endpointMessageReceived } from '../../subtitles/actions.any';
|
||||
import { setIAmVisitor } from '../../visitors/actions';
|
||||
import { iAmVisitor } from '../../visitors/functions';
|
||||
import { overwriteConfig } from '../config/actions';
|
||||
import { getReplaceParticipant } from '../config/functions';
|
||||
import { hangup } from '../connection/actions';
|
||||
import { connect, disconnect, hangup } from '../connection/actions';
|
||||
import { JITSI_CONNECTION_CONFERENCE_KEY } from '../connection/constants';
|
||||
import { JitsiConferenceEvents, JitsiE2ePingEvents } from '../lib-jitsi-meet';
|
||||
import { setAudioMuted, setAudioUnmutePermissions, setVideoMuted, setVideoUnmutePermissions } from '../media/actions';
|
||||
@@ -73,6 +75,7 @@ import {
|
||||
getConferenceOptions,
|
||||
getConferenceState,
|
||||
getCurrentConference,
|
||||
getVisitorOptions,
|
||||
sendLocalParticipant
|
||||
} from './functions';
|
||||
import logger from './logger';
|
||||
@@ -983,3 +986,41 @@ export function setAssumedBandwidthBps(assumedBandwidthBps: number) {
|
||||
assumedBandwidthBps
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Redirects to a new visitor node.
|
||||
*
|
||||
* @param {string | undefined} vnode - The vnode to use or undefined if moving back to the main room.
|
||||
* @param {string} focusJid - The focus jid to use.
|
||||
* @param {string} username - The username to use.
|
||||
* @returns {void}
|
||||
*/
|
||||
export function redirect(vnode: string, focusJid: string, username: string) {
|
||||
return (dispatch: IStore['dispatch'], getState: IStore['getState']) => {
|
||||
const { conference, joining } = getState()['features/base/conference'];
|
||||
|
||||
const newConfig = getVisitorOptions(getState, vnode, focusJid, username);
|
||||
|
||||
if (!newConfig) {
|
||||
logger.warn('Not redirected missing params');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
dispatch(overwriteConfig(newConfig)) // @ts-ignore
|
||||
.then(() => dispatch(conferenceWillLeave(conference || joining)))
|
||||
.then(() => dispatch(disconnect()))
|
||||
.then(() => dispatch(setIAmVisitor(Boolean(vnode))))
|
||||
|
||||
// we do not clear local tracks on error, so we need to manually clear them
|
||||
.then(() => dispatch(destroyLocalTracks()))
|
||||
.then(() => dispatch(conferenceWillInit()))
|
||||
.then(() => dispatch(connect()))
|
||||
.then(() => {
|
||||
// FIXME: Workaround for the web version. To be removed once we get rid of conference.js
|
||||
if (typeof APP !== 'undefined') {
|
||||
APP.conference.startConference([]);
|
||||
}
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
@@ -284,12 +284,12 @@ export function restoreConferenceOptions(stateful: IStateful) {
|
||||
* Override the global config (that is, window.config) with XMPP configuration required to join as a visitor.
|
||||
*
|
||||
* @param {IStateful} stateful - The redux store state.
|
||||
* @param {Array<string>} params - The received parameters.
|
||||
* @param {string|undefined} vnode - The received parameters.
|
||||
* @param {string} focusJid - The received parameters.
|
||||
* @param {string|undefined} username - The received parameters.
|
||||
* @returns {Object}
|
||||
*/
|
||||
export function getVisitorOptions(stateful: IStateful, params: Array<string>) {
|
||||
const [ vnode, focusJid, username ] = params;
|
||||
|
||||
export function getVisitorOptions(stateful: IStateful, vnode: string, focusJid: string, username: string) {
|
||||
const config = toState(stateful)['features/base/config'];
|
||||
|
||||
if (!config?.hosts) {
|
||||
|
||||
@@ -18,7 +18,6 @@ import { showErrorNotification } from '../../notifications/actions';
|
||||
import { NOTIFICATION_TIMEOUT_TYPE } from '../../notifications/constants';
|
||||
import { stopLocalVideoRecording } from '../../recording/actions.any';
|
||||
import LocalRecordingManager from '../../recording/components/Recording/LocalRecordingManager';
|
||||
import { setIAmVisitor } from '../../visitors/actions';
|
||||
import { iAmVisitor } from '../../visitors/functions';
|
||||
import { overwriteConfig } from '../config/actions';
|
||||
import { CONNECTION_ESTABLISHED, CONNECTION_FAILED } from '../connection/actionTypes';
|
||||
@@ -34,7 +33,6 @@ import {
|
||||
} from '../participants/functions';
|
||||
import MiddlewareRegistry from '../redux/MiddlewareRegistry';
|
||||
import { TRACK_ADDED, TRACK_REMOVED } from '../tracks/actionTypes';
|
||||
import { destroyLocalTracks } from '../tracks/actions.any';
|
||||
import { getLocalTracks } from '../tracks/functions.any';
|
||||
|
||||
import {
|
||||
@@ -51,7 +49,6 @@ import {
|
||||
import {
|
||||
authStatusChanged,
|
||||
conferenceFailed,
|
||||
conferenceWillInit,
|
||||
conferenceWillLeave,
|
||||
createConference,
|
||||
setLocalSubject,
|
||||
@@ -63,7 +60,6 @@ import {
|
||||
_removeLocalTracksFromConference,
|
||||
forEachConference,
|
||||
getCurrentConference,
|
||||
getVisitorOptions,
|
||||
restoreConferenceOptions
|
||||
} from './functions';
|
||||
import logger from './logger';
|
||||
@@ -202,34 +198,6 @@ function _conferenceFailed({ dispatch, getState }: IStore, next: Function, actio
|
||||
case JitsiConferenceErrors.OFFER_ANSWER_FAILED:
|
||||
sendAnalytics(createOfferAnswerFailedEvent());
|
||||
break;
|
||||
|
||||
case JitsiConferenceErrors.REDIRECTED: {
|
||||
const newConfig = getVisitorOptions(getState, error.params);
|
||||
|
||||
if (!newConfig) {
|
||||
logger.warn('Not redirected missing params');
|
||||
break;
|
||||
}
|
||||
|
||||
const [ vnode ] = error.params;
|
||||
|
||||
dispatch(overwriteConfig(newConfig)) // @ts-ignore
|
||||
.then(() => dispatch(conferenceWillLeave(conference)))
|
||||
.then(() => dispatch(disconnect()))
|
||||
.then(() => dispatch(setIAmVisitor(Boolean(vnode))))
|
||||
|
||||
// we do not clear local tracks on error, so we need to manually clear them
|
||||
.then(() => dispatch(destroyLocalTracks()))
|
||||
.then(() => dispatch(conferenceWillInit()))
|
||||
.then(() => dispatch(connect()))
|
||||
.then(() => {
|
||||
// FIXME: Workaround for the web version. To be removed once we get rid of conference.js
|
||||
if (typeof APP !== 'undefined') {
|
||||
APP.conference.startConference([]);
|
||||
}
|
||||
});
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
!error.recoverable
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -182,6 +182,7 @@ export interface IConfig {
|
||||
_screenshotHistoryRegionUrl?: number;
|
||||
analytics?: {
|
||||
amplitudeAPPKey?: string;
|
||||
amplitudeIncludeUTM?: boolean;
|
||||
blackListedEvents?: string[];
|
||||
disabled?: boolean;
|
||||
googleAnalyticsTrackingId?: string;
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
import _ from 'lodash';
|
||||
|
||||
import { IReduxState, IStore } from '../../app/types';
|
||||
import { conferenceLeft, conferenceWillLeave } from '../conference/actions';
|
||||
import { conferenceLeft, conferenceWillLeave, redirect } from '../conference/actions';
|
||||
import { getCurrentConference } from '../conference/functions';
|
||||
import { IConfigState } from '../config/reducer';
|
||||
import JitsiMeetJS, { JitsiConnectionEvents } from '../lib-jitsi-meet';
|
||||
import { parseURLParams } from '../util/parseURLParams';
|
||||
import {
|
||||
appendURLParam,
|
||||
getBackendSafeRoomName
|
||||
@@ -18,53 +20,14 @@ import {
|
||||
} from './actionTypes';
|
||||
import { JITSI_CONNECTION_URL_KEY } from './constants';
|
||||
import logger from './logger';
|
||||
import { ConnectionFailedError, IIceServers } from './types';
|
||||
|
||||
/**
|
||||
* The error structure passed to the {@link connectionFailed} action.
|
||||
*
|
||||
* Note there was an intention to make the error resemble an Error instance (to
|
||||
* the extent that jitsi-meet needs it).
|
||||
* The options that will be passed to the JitsiConnection instance.
|
||||
*/
|
||||
export type ConnectionFailedError = {
|
||||
|
||||
/**
|
||||
* The invalid credentials that were used to authenticate and the
|
||||
* authentication failed.
|
||||
*/
|
||||
credentials?: {
|
||||
|
||||
/**
|
||||
* The XMPP user's ID.
|
||||
*/
|
||||
jid: string;
|
||||
|
||||
/**
|
||||
* The XMPP user's password.
|
||||
*/
|
||||
password: string;
|
||||
};
|
||||
|
||||
/**
|
||||
* The details about the connection failed event.
|
||||
*/
|
||||
details?: Object;
|
||||
|
||||
/**
|
||||
* Error message.
|
||||
*/
|
||||
message?: string;
|
||||
|
||||
/**
|
||||
* One of {@link JitsiConnectionError} constants (defined in
|
||||
* lib-jitsi-meet).
|
||||
*/
|
||||
name: string;
|
||||
|
||||
/**
|
||||
* Indicates whether this event is recoverable or not.
|
||||
*/
|
||||
recoverable?: boolean;
|
||||
};
|
||||
interface IOptions extends IConfigState {
|
||||
iceServersOverride?: IIceServers;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an action for when the signaling connection has been lost.
|
||||
@@ -147,7 +110,15 @@ export function connectionFailed(
|
||||
export function constructOptions(state: IReduxState) {
|
||||
// Deep clone the options to make sure we don't modify the object in the
|
||||
// redux store.
|
||||
const options = _.cloneDeep(state['features/base/config']);
|
||||
const options: IOptions = _.cloneDeep(state['features/base/config']);
|
||||
|
||||
const { locationURL } = state['features/base/connection'];
|
||||
const params = parseURLParams(locationURL || '');
|
||||
const iceServersOverride = params['iceServers.replace'];
|
||||
|
||||
if (iceServersOverride) {
|
||||
options.iceServersOverride = iceServersOverride;
|
||||
}
|
||||
|
||||
const { bosh } = options;
|
||||
let { websocket } = options;
|
||||
@@ -230,6 +201,9 @@ export function _connectInternal(id?: string, password?: string) {
|
||||
connection.addEventListener(
|
||||
JitsiConnectionEvents.CONNECTION_FAILED,
|
||||
_onConnectionFailed);
|
||||
connection.addEventListener(
|
||||
JitsiConnectionEvents.CONNECTION_REDIRECTED,
|
||||
_onConnectionRedirected);
|
||||
|
||||
/**
|
||||
* Unsubscribe the connection instance from
|
||||
@@ -298,9 +272,28 @@ export function _connectInternal(id?: string, password?: string) {
|
||||
resolve(connection);
|
||||
}
|
||||
|
||||
/**
|
||||
* Rejects external promise when connection fails.
|
||||
*
|
||||
* @param {string|undefined} vnode - The vnode to connect to.
|
||||
* @param {string} focusJid - The focus jid to use.
|
||||
* @param {string|undefined} username - The username to use when joining. This is after promotion from
|
||||
* visitor to main participant.
|
||||
* @private
|
||||
* @returns {void}
|
||||
*/
|
||||
function _onConnectionRedirected(vnode: string, focusJid: string, username: string) {
|
||||
connection.removeEventListener(JitsiConnectionEvents.CONNECTION_REDIRECTED, _onConnectionRedirected);
|
||||
dispatch(redirect(vnode, focusJid, username));
|
||||
}
|
||||
|
||||
// in case of configured http url for conference request we need the room name
|
||||
const name = getBackendSafeRoomName(state['features/base/conference'].room);
|
||||
|
||||
connection.connect({
|
||||
id,
|
||||
password
|
||||
password,
|
||||
name
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
@@ -11,7 +11,7 @@ import {
|
||||
SET_LOCATION_URL,
|
||||
SHOW_CONNECTION_INFO
|
||||
} from './actionTypes';
|
||||
import { ConnectionFailedError } from './actions.any';
|
||||
import { ConnectionFailedError } from './types';
|
||||
|
||||
export interface IConnectionState {
|
||||
connecting?: any;
|
||||
|
||||
113
react/features/base/connection/types.ts
Normal file
113
react/features/base/connection/types.ts
Normal file
@@ -0,0 +1,113 @@
|
||||
/**
|
||||
* The error structure passed to the {@link connectionFailed} action.
|
||||
*
|
||||
* Note there was an intention to make the error resemble an Error instance (to
|
||||
* the extent that jitsi-meet needs it).
|
||||
*/
|
||||
export type ConnectionFailedError = {
|
||||
|
||||
/**
|
||||
* The invalid credentials that were used to authenticate and the
|
||||
* authentication failed.
|
||||
*/
|
||||
credentials?: {
|
||||
|
||||
/**
|
||||
* The XMPP user's ID.
|
||||
*/
|
||||
jid: string;
|
||||
|
||||
/**
|
||||
* The XMPP user's password.
|
||||
*/
|
||||
password: string;
|
||||
};
|
||||
|
||||
/**
|
||||
* The details about the connection failed event.
|
||||
*/
|
||||
details?: Object;
|
||||
|
||||
/**
|
||||
* Error message.
|
||||
*/
|
||||
message?: string;
|
||||
|
||||
/**
|
||||
* One of {@link JitsiConnectionError} constants (defined in
|
||||
* lib-jitsi-meet).
|
||||
*/
|
||||
name: string;
|
||||
|
||||
/**
|
||||
* Indicates whether this event is recoverable or not.
|
||||
*/
|
||||
recoverable?: boolean;
|
||||
};
|
||||
|
||||
/**
|
||||
* The value for the username or credential property.
|
||||
*/
|
||||
type ReplaceIceServersField = string | null;
|
||||
|
||||
/**
|
||||
* The value for the urls property.
|
||||
*/
|
||||
type IceServerUrls = null | string | Array<string>;
|
||||
|
||||
/**
|
||||
* The types of ice servers.
|
||||
*/
|
||||
enum IceServerType {
|
||||
STUN = 'stun',
|
||||
TURN = 'turn',
|
||||
TURNS = 'turns'
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents a single override rule.
|
||||
*/
|
||||
interface IReplaceIceServer {
|
||||
|
||||
/**
|
||||
* The value the credential prop will be replaced with.
|
||||
*
|
||||
* NOTE: If the value is null we will remove the credential property in entry that matches the target type. If the
|
||||
* value is undefined or missing we won't change the credential property in the entry that matches the target type.
|
||||
*/
|
||||
credential?: ReplaceIceServersField;
|
||||
|
||||
/**
|
||||
* Target type that will be used to match the already received ice server and modify/remove it based on the values
|
||||
* of credential, urls and username.
|
||||
*/
|
||||
targetType: IceServerType;
|
||||
|
||||
/**
|
||||
* The value the urls prop will be replaced with.
|
||||
*
|
||||
* NOTE: If the value is null we will remove the whole entry that matches the target type. If the value is undefined
|
||||
* or missing we won't change the urls property in the entry that matches the target type.
|
||||
*/
|
||||
urls?: IceServerUrls;
|
||||
|
||||
/**
|
||||
* The value the username prop will be replaced with.
|
||||
*
|
||||
* NOTE: If the value is null we will remove the username property in entry that matches the target type. If the
|
||||
* value is undefined or missing we won't change the username property in the entry that matches the target type.
|
||||
*/
|
||||
username?: ReplaceIceServersField;
|
||||
}
|
||||
|
||||
/**
|
||||
* An object with rules for changing the existing ice server configuration.
|
||||
*/
|
||||
export interface IIceServers {
|
||||
|
||||
/**
|
||||
* An array of rules for replacing parts from the existing ice server configuration.
|
||||
*/
|
||||
replace: Array<IReplaceIceServer>;
|
||||
}
|
||||
|
||||
@@ -9,10 +9,12 @@ import { NOTIFICATION_TIMEOUT_TYPE } from '../../notifications/constants';
|
||||
import { replaceAudioTrackById, replaceVideoTrackById, setDeviceStatusWarning } from '../../prejoin/actions';
|
||||
import { isPrejoinPageVisible } from '../../prejoin/functions';
|
||||
import { APP_WILL_MOUNT, APP_WILL_UNMOUNT } from '../app/actionTypes';
|
||||
import { isMobileBrowser } from '../environment/utils';
|
||||
import JitsiMeetJS, { JitsiMediaDevicesEvents, JitsiTrackErrors } from '../lib-jitsi-meet';
|
||||
import { MEDIA_TYPE } from '../media/constants';
|
||||
import MiddlewareRegistry from '../redux/MiddlewareRegistry';
|
||||
import { updateSettings } from '../settings/actions';
|
||||
|
||||
import { getLocalTrack } from '../tracks/functions';
|
||||
|
||||
import {
|
||||
CHECK_AND_NOTIFY_FOR_NEW_DEVICE,
|
||||
@@ -182,13 +184,20 @@ MiddlewareRegistry.register(store => next => action => {
|
||||
APP.UI.emitEvent(UIEvents.AUDIO_DEVICE_CHANGED, action.deviceId);
|
||||
}
|
||||
break;
|
||||
case SET_VIDEO_INPUT_DEVICE:
|
||||
case SET_VIDEO_INPUT_DEVICE: {
|
||||
const localTrack = getLocalTrack(store.getState()['features/base/tracks'], MEDIA_TYPE.VIDEO);
|
||||
|
||||
// on mobile devices the video stream has to be stopped before replacing it
|
||||
if (isMobileBrowser() && localTrack && !localTrack.muted) {
|
||||
localTrack.jitsiTrack.stopStream();
|
||||
}
|
||||
if (isPrejoinPageVisible(store.getState())) {
|
||||
store.dispatch(replaceVideoTrackById(action.deviceId));
|
||||
} else {
|
||||
APP.UI.emitEvent(UIEvents.VIDEO_DEVICE_CHANGED, action.deviceId);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case UPDATE_DEVICE_LIST:
|
||||
logDeviceList(groupDevicesByKind(action.devices));
|
||||
if (areDeviceLabelsInitialized(store.getState())) {
|
||||
|
||||
@@ -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)
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -18,3 +18,4 @@ export function isMobileBrowser() {
|
||||
export function isIosMobileBrowser() {
|
||||
return Platform.OS === 'ios';
|
||||
}
|
||||
|
||||
|
||||
17
react/features/base/icons/components/SvgXmlIcon.native.tsx
Normal file
17
react/features/base/icons/components/SvgXmlIcon.native.tsx
Normal file
@@ -0,0 +1,17 @@
|
||||
import React from 'react';
|
||||
import { SvgFromXml } from 'react-native-svg';
|
||||
|
||||
/**
|
||||
* SVG rendering component.
|
||||
*
|
||||
* @returns {JSX.Element}
|
||||
*/
|
||||
const SvgXmlIcon = ({ src, ...rest }: {
|
||||
src: string;
|
||||
}): JSX.Element => (
|
||||
<SvgFromXml
|
||||
override = { rest }
|
||||
xml = { src } />
|
||||
);
|
||||
|
||||
export default SvgXmlIcon;
|
||||
26
react/features/base/icons/components/SvgXmlIcon.web.tsx
Normal file
26
react/features/base/icons/components/SvgXmlIcon.web.tsx
Normal file
@@ -0,0 +1,26 @@
|
||||
import React, { useMemo } from 'react';
|
||||
|
||||
/**
|
||||
* SVG rendering component.
|
||||
*
|
||||
* @returns {JSX.Element}
|
||||
*/
|
||||
const SvgXmlIcon = ({ src, ...rest }: {
|
||||
src: string;
|
||||
}): JSX.Element => {
|
||||
const svgDocument = new DOMParser().parseFromString(src, 'image/svg+xml');
|
||||
const element = svgDocument.documentElement.outerHTML;
|
||||
const attributes = useMemo(() => Object.entries(rest).map(
|
||||
([ key, value ]) => `${key}="${value}"`)
|
||||
.join(' '), [ rest ]);
|
||||
|
||||
const html = element.replace('<svg', `<svg ${attributes}`);
|
||||
|
||||
return (
|
||||
<div // eslint-disable-next-line react/no-danger
|
||||
dangerouslySetInnerHTML = {{ __html: html }}
|
||||
{ ...rest } />
|
||||
);
|
||||
};
|
||||
|
||||
export default SvgXmlIcon;
|
||||
32
react/features/base/icons/components/withBranding.tsx
Normal file
32
react/features/base/icons/components/withBranding.tsx
Normal file
@@ -0,0 +1,32 @@
|
||||
import React from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
|
||||
import { IReduxState } from '../../../app/types';
|
||||
|
||||
import SvgXmlIcon from './SvgXmlIcon';
|
||||
|
||||
/**
|
||||
* Icon wrapper that checks for branding before returning the SVG component.
|
||||
*
|
||||
* @returns {JSX.Element}
|
||||
*/
|
||||
const withBranding = ({ DefaultIcon, iconName }: {
|
||||
DefaultIcon: any;
|
||||
iconName: string;
|
||||
}) => (props: any) => {
|
||||
const src = useSelector((state: IReduxState) =>
|
||||
state['features/dynamic-branding']?.brandedIcons?.[iconName]
|
||||
);
|
||||
|
||||
if (src) {
|
||||
return (
|
||||
<SvgXmlIcon
|
||||
src = { src }
|
||||
{ ...props } />
|
||||
);
|
||||
}
|
||||
|
||||
return <DefaultIcon { ...props } />;
|
||||
};
|
||||
|
||||
export default withBranding;
|
||||
224
react/features/base/icons/svg/constants.ts
Normal file
224
react/features/base/icons/svg/constants.ts
Normal file
@@ -0,0 +1,224 @@
|
||||
import { default as IconRecordAccount } from './account-record.svg';
|
||||
import { default as IconAddUser } from './add-user.svg';
|
||||
import { default as IconArrowBack } from './arrow-back.svg';
|
||||
import { default as IconArrowDownLarge } from './arrow-down-large.svg';
|
||||
import { default as IconArrowDown } from './arrow-down.svg';
|
||||
import { default as IconArrowLeft } from './arrow-left.svg';
|
||||
import { default as IconArrowRight } from './arrow-right.svg';
|
||||
import { default as IconArrowUpLarge } from './arrow-up-large.svg';
|
||||
import { default as IconArrowUp } from './arrow-up.svg';
|
||||
import { default as IconBell } from './bell.svg';
|
||||
import { default as IconBluetooth } from './bluetooth.svg';
|
||||
import { default as IconCalendar } from './calendar.svg';
|
||||
import { default as IconCameraRefresh } from './camera-refresh.svg';
|
||||
import { default as IconCar } from './car.svg';
|
||||
import { default as IconChatUnread } from './chat-unread.svg';
|
||||
import { default as IconCheck } from './check.svg';
|
||||
import { default as IconCloseCircle } from './close-circle.svg';
|
||||
import { default as IconCloseLarge } from './close-large.svg';
|
||||
import { default as IconCloudUpload } from './cloud-upload.svg';
|
||||
import { default as IconCode } from './code.svg';
|
||||
import { default as IconConnection } from './connection.svg';
|
||||
import { default as IconRecordContact } from './contact-record.svg';
|
||||
import { default as IconCopy } from './copy.svg';
|
||||
import { default as IconDotsHorizontal } from './dots-horizontal.svg';
|
||||
import { default as IconDownload } from './download.svg';
|
||||
import { default as IconE2EE } from './e2ee.svg';
|
||||
import { default as IconEdit } from './edit.svg';
|
||||
import { default as IconEmotionsAngry } from './emotions-angry.svg';
|
||||
import { default as IconEmotionsDisgusted } from './emotions-disgusted.svg';
|
||||
import { default as IconEmotionsFearful } from './emotions-fearful.svg';
|
||||
import { default as IconEmotionsHappy } from './emotions-happy.svg';
|
||||
import { default as IconEmotionsNeutral } from './emotions-neutral.svg';
|
||||
import { default as IconEmotionsSad } from './emotions-sad.svg';
|
||||
import { default as IconEmotionsSurprised } from './emotions-surprised.svg';
|
||||
import { default as IconEnlarge } from './enlarge.svg';
|
||||
import { default as IconEnterFullscreen } from './enter-fullscreen.svg';
|
||||
import { default as IconEnvelope } from './envelope.svg';
|
||||
import { default as IconExclamationSolid } from './exclamation-solid.svg';
|
||||
import { default as IconExclamationTriangle } from './exclamation-triangle.svg';
|
||||
import { default as IconExitFullscreen } from './exit-fullscreen.svg';
|
||||
import { default as IconFaceSmile } from './face-smile.svg';
|
||||
import { default as IconFavoriteSolid } from './favorite-solid.svg';
|
||||
import { default as IconFavorite } from './favorite.svg';
|
||||
import { default as IconFeedback } from './feedback.svg';
|
||||
import { default as IconGear } from './gear.svg';
|
||||
import { default as IconGoogle } from './google.svg';
|
||||
import { default as IconHangup } from './hangup.svg';
|
||||
import { default as IconDeviceHeadphone } from './headset.svg';
|
||||
import { default as IconHelp } from './help.svg';
|
||||
import { default as IconHighlight } from './highlight.svg';
|
||||
import { default as IconRingGroup } from './icon-ring-group.svg';
|
||||
import { default as IconImage } from './image.svg';
|
||||
import { default as IconInfoCircle } from './info-circle.svg';
|
||||
import { default as IconInfo } from './info.svg';
|
||||
import { default as IconRecordLead } from './lead-record.svg';
|
||||
import { default as IconMessage } from './message.svg';
|
||||
import { default as IconMeter } from './meter.svg';
|
||||
import { default as IconMicSlash } from './mic-slash.svg';
|
||||
import { default as IconMic } from './mic.svg';
|
||||
import { default as IconModerator } from './moderator.svg';
|
||||
import { default as IconConnectionInactive } from './ninja.svg';
|
||||
import { default as IconNoiseSuppressionOff } from './noise-suppression-off.svg';
|
||||
import { default as IconNoiseSuppressionOn } from './noise-suppression-on.svg';
|
||||
import { default as IconOffice365 } from './office365.svg';
|
||||
import { default as IconRecordOpportunity } from './opportunity-record.svg';
|
||||
import { default as IconPerformance } from './performance.svg';
|
||||
import { default as IconPhoneRinging } from './phone-ringing.svg';
|
||||
import { default as IconPin } from './pin.svg';
|
||||
import { default as IconPinned } from './pinned.svg';
|
||||
import { default as IconPlay } from './play.svg';
|
||||
import { default as IconPlus } from './plus.svg';
|
||||
import { default as IconRaiseHand } from './raise-hand.svg';
|
||||
import { default as IconRecord } from './record.svg';
|
||||
import { default as IconReply } from './reply.svg';
|
||||
import { default as IconRestore } from './restore.svg';
|
||||
import { default as IconScreenshare } from './screenshare.svg';
|
||||
import { default as IconSearch } from './search.svg';
|
||||
import { default as IconSecurityOff } from './security-off.svg';
|
||||
import { default as IconSecurityOn } from './security-on.svg';
|
||||
import { default as IconSend } from './send.svg';
|
||||
import { default as IconShareDoc } from './share-doc.svg';
|
||||
import { default as IconShare } from './share.svg';
|
||||
import { default as IconShortcuts } from './shortcuts.svg';
|
||||
import { default as IconSip } from './sip.svg';
|
||||
import { default as IconSites } from './sites.svg';
|
||||
import { default as IconRemoteControlStart } from './start-remote-control.svg';
|
||||
import { default as IconRemoteControlStop } from './stop-remote-control.svg';
|
||||
import { default as IconStopScreenshare } from './stop-screenshare.svg';
|
||||
import { default as IconStop } from './stop.svg';
|
||||
import { default as IconSubtitles } from './subtitles.svg';
|
||||
import { default as IconTileView } from './tile-view.svg';
|
||||
import { default as IconTrash } from './trash.svg';
|
||||
import { default as IconUserDeleted } from './user-deleted.svg';
|
||||
import { default as IconUser } from './user.svg';
|
||||
import { default as IconUsers } from './users.svg';
|
||||
import { default as IconVideoOff } from './video-off.svg';
|
||||
import { default as IconVideo } from './video.svg';
|
||||
import { default as IconAudioOnlyOff } from './visibility-off.svg';
|
||||
import { default as IconAudioOnly } from './visibility.svg';
|
||||
import { default as IconVolumeOff } from './volume-off.svg';
|
||||
import { default as IconVolumeUp } from './volume-up.svg';
|
||||
import { default as IconWarningCircle } from './warning-circle.svg';
|
||||
import { default as IconWarning } from './warning.svg';
|
||||
import { default as IconWhiteboardHide } from './whiteboard-hide.svg';
|
||||
import { default as IconWhiteboard } from './whiteboard.svg';
|
||||
import { default as IconWifi1Bar } from './wifi-1.svg';
|
||||
import { default as IconWifi2Bars } from './wifi-2.svg';
|
||||
import { default as IconWifi3Bars } from './wifi-3.svg';
|
||||
import { default as IconYahoo } from './yahoo.svg';
|
||||
|
||||
/**
|
||||
* Map containing the default icons.
|
||||
*/
|
||||
export const DEFAULT_ICON: Record<string, any> = {
|
||||
IconAddUser,
|
||||
IconArrowBack,
|
||||
IconArrowDown,
|
||||
IconArrowDownLarge,
|
||||
IconArrowLeft,
|
||||
IconArrowUp,
|
||||
IconArrowUpLarge,
|
||||
IconAudioOnly,
|
||||
IconAudioOnlyOff,
|
||||
IconBluetooth,
|
||||
IconBell,
|
||||
IconCalendar,
|
||||
IconCameraRefresh,
|
||||
IconCar,
|
||||
IconChatUnread,
|
||||
IconCheck,
|
||||
IconCloseCircle,
|
||||
IconCloseLarge,
|
||||
IconCloudUpload,
|
||||
IconCode,
|
||||
IconConnection,
|
||||
IconConnectionInactive,
|
||||
IconCopy,
|
||||
IconDeviceHeadphone,
|
||||
IconDotsHorizontal,
|
||||
IconDownload,
|
||||
IconE2EE,
|
||||
IconEdit,
|
||||
IconEnlarge,
|
||||
IconEnterFullscreen,
|
||||
IconEnvelope,
|
||||
IconEmotionsAngry,
|
||||
IconEmotionsDisgusted,
|
||||
IconEmotionsFearful,
|
||||
IconEmotionsHappy,
|
||||
IconEmotionsNeutral,
|
||||
IconEmotionsSad,
|
||||
IconEmotionsSurprised,
|
||||
IconExclamationSolid,
|
||||
IconExclamationTriangle,
|
||||
IconExitFullscreen,
|
||||
IconFaceSmile,
|
||||
IconFavorite,
|
||||
IconFavoriteSolid,
|
||||
IconFeedback,
|
||||
IconGear,
|
||||
IconGoogle,
|
||||
IconHangup,
|
||||
IconHelp,
|
||||
IconHighlight,
|
||||
IconImage,
|
||||
IconInfo,
|
||||
IconInfoCircle,
|
||||
IconMessage,
|
||||
IconMeter,
|
||||
IconMic,
|
||||
IconMicSlash,
|
||||
IconModerator,
|
||||
IconNoiseSuppressionOff,
|
||||
IconNoiseSuppressionOn,
|
||||
IconArrowRight,
|
||||
IconOffice365,
|
||||
IconPerformance,
|
||||
IconPhoneRinging,
|
||||
IconPin,
|
||||
IconPinned,
|
||||
IconPlay,
|
||||
IconPlus,
|
||||
IconRaiseHand,
|
||||
IconRecord,
|
||||
IconRecordAccount,
|
||||
IconRecordContact,
|
||||
IconRecordLead,
|
||||
IconRecordOpportunity,
|
||||
IconRemoteControlStart,
|
||||
IconRemoteControlStop,
|
||||
IconReply,
|
||||
IconRestore,
|
||||
IconRingGroup,
|
||||
IconScreenshare,
|
||||
IconSearch,
|
||||
IconSecurityOff,
|
||||
IconSecurityOn,
|
||||
IconSend,
|
||||
IconShare,
|
||||
IconShareDoc,
|
||||
IconShortcuts,
|
||||
IconSip,
|
||||
IconSites,
|
||||
IconStop,
|
||||
IconStopScreenshare,
|
||||
IconSubtitles,
|
||||
IconTileView,
|
||||
IconTrash,
|
||||
IconUserDeleted,
|
||||
IconUsers,
|
||||
IconUser,
|
||||
IconVideo,
|
||||
IconVideoOff,
|
||||
IconVolumeOff,
|
||||
IconVolumeUp,
|
||||
IconWarning,
|
||||
IconWarningCircle,
|
||||
IconWhiteboard,
|
||||
IconWhiteboardHide,
|
||||
IconWifi1Bar,
|
||||
IconWifi2Bars,
|
||||
IconWifi3Bars,
|
||||
IconYahoo
|
||||
};
|
||||
@@ -1,109 +1,235 @@
|
||||
export { default as IconAddUser } from './add-user.svg';
|
||||
export { default as IconArrowBack } from './arrow-back.svg';
|
||||
export { default as IconArrowDown } from './arrow-down.svg';
|
||||
export { default as IconArrowDownLarge } from './arrow-down-large.svg';
|
||||
export { default as IconArrowLeft } from './arrow-left.svg';
|
||||
export { default as IconArrowUp } from './arrow-up.svg';
|
||||
export { default as IconArrowUpLarge } from './arrow-up-large.svg';
|
||||
export { default as IconAudioOnly } from './visibility.svg';
|
||||
export { default as IconAudioOnlyOff } from './visibility-off.svg';
|
||||
export { default as IconBluetooth } from './bluetooth.svg';
|
||||
export { default as IconBell } from './bell.svg';
|
||||
export { default as IconCalendar } from './calendar.svg';
|
||||
export { default as IconCameraRefresh } from './camera-refresh.svg';
|
||||
export { default as IconCar } from './car.svg';
|
||||
export { default as IconChatUnread } from './chat-unread.svg';
|
||||
export { default as IconCheck } from './check.svg';
|
||||
export { default as IconCloseCircle } from './close-circle.svg';
|
||||
export { default as IconCloseLarge } from './close-large.svg';
|
||||
export { default as IconCloudUpload } from './cloud-upload.svg';
|
||||
export { default as IconCode } from './code.svg';
|
||||
export { default as IconConnection } from './connection.svg';
|
||||
export { default as IconConnectionInactive } from './ninja.svg';
|
||||
export { default as IconCopy } from './copy.svg';
|
||||
export { default as IconDeviceHeadphone } from './headset.svg';
|
||||
export { default as IconDotsHorizontal } from './dots-horizontal.svg';
|
||||
export { default as IconDownload } from './download.svg';
|
||||
export { default as IconE2EE } from './e2ee.svg';
|
||||
export { default as IconEdit } from './edit.svg';
|
||||
export { default as IconEnlarge } from './enlarge.svg';
|
||||
export { default as IconEnterFullscreen } from './enter-fullscreen.svg';
|
||||
export { default as IconEnvelope } from './envelope.svg';
|
||||
export { default as IconEmotionsAngry } from './emotions-angry.svg';
|
||||
export { default as IconEmotionsDisgusted } from './emotions-disgusted.svg';
|
||||
export { default as IconEmotionsFearful } from './emotions-fearful.svg';
|
||||
export { default as IconEmotionsHappy } from './emotions-happy.svg';
|
||||
export { default as IconEmotionsNeutral } from './emotions-neutral.svg';
|
||||
export { default as IconEmotionsSad } from './emotions-sad.svg';
|
||||
export { default as IconEmotionsSurprised } from './emotions-surprised.svg';
|
||||
export { default as IconExclamationSolid } from './exclamation-solid.svg';
|
||||
export { default as IconExclamationTriangle } from './exclamation-triangle.svg';
|
||||
export { default as IconExitFullscreen } from './exit-fullscreen.svg';
|
||||
export { default as IconFaceSmile } from './face-smile.svg';
|
||||
export { default as IconFavorite } from './favorite.svg';
|
||||
export { default as IconFavoriteSolid } from './favorite-solid.svg';
|
||||
export { default as IconFeedback } from './feedback.svg';
|
||||
export { default as IconGear } from './gear.svg';
|
||||
export { default as IconGoogle } from './google.svg';
|
||||
export { default as IconHangup } from './hangup.svg';
|
||||
export { default as IconHelp } from './help.svg';
|
||||
export { default as IconHighlight } from './highlight.svg';
|
||||
export { default as IconImage } from './image.svg';
|
||||
export { default as IconInfo } from './info.svg';
|
||||
export { default as IconInfoCircle } from './info-circle.svg';
|
||||
export { default as IconMessage } from './message.svg';
|
||||
export { default as IconMeter } from './meter.svg';
|
||||
export { default as IconMic } from './mic.svg';
|
||||
export { default as IconMicSlash } from './mic-slash.svg';
|
||||
export { default as IconModerator } from './moderator.svg';
|
||||
export { default as IconNoiseSuppressionOff } from './noise-suppression-off.svg';
|
||||
export { default as IconNoiseSuppressionOn } from './noise-suppression-on.svg';
|
||||
export { default as IconArrowRight } from './arrow-right.svg';
|
||||
export { default as IconOffice365 } from './office365.svg';
|
||||
export { default as IconPerformance } from './performance.svg';
|
||||
export { default as IconPhoneRinging } from './phone-ringing.svg';
|
||||
export { default as IconPin } from './pin.svg';
|
||||
export { default as IconPinned } from './pinned.svg';
|
||||
export { default as IconPlay } from './play.svg';
|
||||
export { default as IconPlus } from './plus.svg';
|
||||
export { default as IconRaiseHand } from './raise-hand.svg';
|
||||
export { default as IconRecord } from './record.svg';
|
||||
export { default as IconRecordAccount } from './account-record.svg';
|
||||
export { default as IconRecordContact } from './contact-record.svg';
|
||||
export { default as IconRecordLead } from './lead-record.svg';
|
||||
export { default as IconRecordOpportunity } from './opportunity-record.svg';
|
||||
export { default as IconRemoteControlStart } from './start-remote-control.svg';
|
||||
export { default as IconRemoteControlStop } from './stop-remote-control.svg';
|
||||
export { default as IconReply } from './reply.svg';
|
||||
export { default as IconRestore } from './restore.svg';
|
||||
export { default as IconRingGroup } from './icon-ring-group.svg';
|
||||
export { default as IconScreenshare } from './screenshare.svg';
|
||||
export { default as IconSearch } from './search.svg';
|
||||
export { default as IconSecurityOff } from './security-off.svg';
|
||||
export { default as IconSecurityOn } from './security-on.svg';
|
||||
export { default as IconSend } from './send.svg';
|
||||
export { default as IconShare } from './share.svg';
|
||||
export { default as IconShareDoc } from './share-doc.svg';
|
||||
export { default as IconShortcuts } from './shortcuts.svg';
|
||||
export { default as IconSip } from './sip.svg';
|
||||
export { default as IconSites } from './sites.svg';
|
||||
export { default as IconStop } from './stop.svg';
|
||||
export { default as IconStopScreenshare } from './stop-screenshare.svg';
|
||||
export { default as IconSubtitles } from './subtitles.svg';
|
||||
export { default as IconTileView } from './tile-view.svg';
|
||||
export { default as IconTrash } from './trash.svg';
|
||||
export { default as IconUserDeleted } from './user-deleted.svg';
|
||||
export { default as IconUsers } from './users.svg';
|
||||
export { default as IconUser } from './user.svg';
|
||||
export { default as IconVideo } from './video.svg';
|
||||
export { default as IconVideoOff } from './video-off.svg';
|
||||
export { default as IconVolumeOff } from './volume-off.svg';
|
||||
export { default as IconVolumeUp } from './volume-up.svg';
|
||||
export { default as IconWarning } from './warning.svg';
|
||||
export { default as IconWarningCircle } from './warning-circle.svg';
|
||||
export { default as IconWhiteboard } from './whiteboard.svg';
|
||||
export { default as IconWhiteboardHide } from './whiteboard-hide.svg';
|
||||
export { default as IconWifi1Bar } from './wifi-1.svg';
|
||||
export { default as IconWifi2Bars } from './wifi-2.svg';
|
||||
export { default as IconWifi3Bars } from './wifi-3.svg';
|
||||
export { default as IconYahoo } from './yahoo.svg';
|
||||
import withBranding from '../components/withBranding';
|
||||
|
||||
import { DEFAULT_ICON } from './constants';
|
||||
|
||||
const {
|
||||
IconAddUser,
|
||||
IconArrowBack,
|
||||
IconArrowDown,
|
||||
IconArrowDownLarge,
|
||||
IconArrowLeft,
|
||||
IconArrowUp,
|
||||
IconArrowUpLarge,
|
||||
IconAudioOnly,
|
||||
IconAudioOnlyOff,
|
||||
IconBluetooth,
|
||||
IconBell,
|
||||
IconCalendar,
|
||||
IconCameraRefresh,
|
||||
IconCar,
|
||||
IconChatUnread,
|
||||
IconCheck,
|
||||
IconCloseCircle,
|
||||
IconCloseLarge,
|
||||
IconCloudUpload,
|
||||
IconCode,
|
||||
IconConnection,
|
||||
IconConnectionInactive,
|
||||
IconCopy,
|
||||
IconDeviceHeadphone,
|
||||
IconDotsHorizontal,
|
||||
IconDownload,
|
||||
IconE2EE,
|
||||
IconEdit,
|
||||
IconEnlarge,
|
||||
IconEnterFullscreen,
|
||||
IconEnvelope,
|
||||
IconEmotionsAngry,
|
||||
IconEmotionsDisgusted,
|
||||
IconEmotionsFearful,
|
||||
IconEmotionsHappy,
|
||||
IconEmotionsNeutral,
|
||||
IconEmotionsSad,
|
||||
IconEmotionsSurprised,
|
||||
IconExclamationSolid,
|
||||
IconExclamationTriangle,
|
||||
IconExitFullscreen,
|
||||
IconFaceSmile,
|
||||
IconFavorite,
|
||||
IconFavoriteSolid,
|
||||
IconFeedback,
|
||||
IconGear,
|
||||
IconGoogle,
|
||||
IconHangup,
|
||||
IconHelp,
|
||||
IconHighlight,
|
||||
IconImage,
|
||||
IconInfo,
|
||||
IconInfoCircle,
|
||||
IconMessage,
|
||||
IconMeter,
|
||||
IconMic,
|
||||
IconMicSlash,
|
||||
IconModerator,
|
||||
IconNoiseSuppressionOff,
|
||||
IconNoiseSuppressionOn,
|
||||
IconArrowRight,
|
||||
IconOffice365,
|
||||
IconPerformance,
|
||||
IconPhoneRinging,
|
||||
IconPin,
|
||||
IconPinned,
|
||||
IconPlay,
|
||||
IconPlus,
|
||||
IconRaiseHand,
|
||||
IconRecord,
|
||||
IconRecordAccount,
|
||||
IconRecordContact,
|
||||
IconRecordLead,
|
||||
IconRecordOpportunity,
|
||||
IconRemoteControlStart,
|
||||
IconRemoteControlStop,
|
||||
IconReply,
|
||||
IconRestore,
|
||||
IconRingGroup,
|
||||
IconScreenshare,
|
||||
IconSearch,
|
||||
IconSecurityOff,
|
||||
IconSecurityOn,
|
||||
IconSend,
|
||||
IconShare,
|
||||
IconShareDoc,
|
||||
IconShortcuts,
|
||||
IconSip,
|
||||
IconSites,
|
||||
IconStop,
|
||||
IconStopScreenshare,
|
||||
IconSubtitles,
|
||||
IconTileView,
|
||||
IconTrash,
|
||||
IconUserDeleted,
|
||||
IconUsers,
|
||||
IconUser,
|
||||
IconVideo,
|
||||
IconVideoOff,
|
||||
IconVolumeOff,
|
||||
IconVolumeUp,
|
||||
IconWarning,
|
||||
IconWarningCircle,
|
||||
IconWhiteboard,
|
||||
IconWhiteboardHide,
|
||||
IconWifi1Bar,
|
||||
IconWifi2Bars,
|
||||
IconWifi3Bars,
|
||||
IconYahoo
|
||||
} = Object.keys(DEFAULT_ICON).reduce((exportedIcons: Record<string, any>, key) => {
|
||||
return {
|
||||
...exportedIcons,
|
||||
[key]: withBranding({
|
||||
iconName: key,
|
||||
DefaultIcon: DEFAULT_ICON[key]
|
||||
})
|
||||
};
|
||||
}, {});
|
||||
|
||||
export {
|
||||
IconAddUser,
|
||||
IconArrowBack,
|
||||
IconArrowDown,
|
||||
IconArrowDownLarge,
|
||||
IconArrowLeft,
|
||||
IconArrowUp,
|
||||
IconArrowUpLarge,
|
||||
IconAudioOnly,
|
||||
IconAudioOnlyOff,
|
||||
IconBluetooth,
|
||||
IconBell,
|
||||
IconCalendar,
|
||||
IconCameraRefresh,
|
||||
IconCar,
|
||||
IconChatUnread,
|
||||
IconCheck,
|
||||
IconCloseCircle,
|
||||
IconCloseLarge,
|
||||
IconCloudUpload,
|
||||
IconCode,
|
||||
IconConnection,
|
||||
IconConnectionInactive,
|
||||
IconCopy,
|
||||
IconDeviceHeadphone,
|
||||
IconDotsHorizontal,
|
||||
IconDownload,
|
||||
IconE2EE,
|
||||
IconEdit,
|
||||
IconEnlarge,
|
||||
IconEnterFullscreen,
|
||||
IconEnvelope,
|
||||
IconEmotionsAngry,
|
||||
IconEmotionsDisgusted,
|
||||
IconEmotionsFearful,
|
||||
IconEmotionsHappy,
|
||||
IconEmotionsNeutral,
|
||||
IconEmotionsSad,
|
||||
IconEmotionsSurprised,
|
||||
IconExclamationSolid,
|
||||
IconExclamationTriangle,
|
||||
IconExitFullscreen,
|
||||
IconFaceSmile,
|
||||
IconFavorite,
|
||||
IconFavoriteSolid,
|
||||
IconFeedback,
|
||||
IconGear,
|
||||
IconGoogle,
|
||||
IconHangup,
|
||||
IconHelp,
|
||||
IconHighlight,
|
||||
IconImage,
|
||||
IconInfo,
|
||||
IconInfoCircle,
|
||||
IconMessage,
|
||||
IconMeter,
|
||||
IconMic,
|
||||
IconMicSlash,
|
||||
IconModerator,
|
||||
IconNoiseSuppressionOff,
|
||||
IconNoiseSuppressionOn,
|
||||
IconArrowRight,
|
||||
IconOffice365,
|
||||
IconPerformance,
|
||||
IconPhoneRinging,
|
||||
IconPin,
|
||||
IconPinned,
|
||||
IconPlay,
|
||||
IconPlus,
|
||||
IconRaiseHand,
|
||||
IconRecord,
|
||||
IconRecordAccount,
|
||||
IconRecordContact,
|
||||
IconRecordLead,
|
||||
IconRecordOpportunity,
|
||||
IconRemoteControlStart,
|
||||
IconRemoteControlStop,
|
||||
IconReply,
|
||||
IconRestore,
|
||||
IconRingGroup,
|
||||
IconScreenshare,
|
||||
IconSearch,
|
||||
IconSecurityOff,
|
||||
IconSecurityOn,
|
||||
IconSend,
|
||||
IconShare,
|
||||
IconShareDoc,
|
||||
IconShortcuts,
|
||||
IconSip,
|
||||
IconSites,
|
||||
IconStop,
|
||||
IconStopScreenshare,
|
||||
IconSubtitles,
|
||||
IconTileView,
|
||||
IconTrash,
|
||||
IconUserDeleted,
|
||||
IconUsers,
|
||||
IconUser,
|
||||
IconVideo,
|
||||
IconVideoOff,
|
||||
IconVolumeOff,
|
||||
IconVolumeUp,
|
||||
IconWarning,
|
||||
IconWarningCircle,
|
||||
IconWhiteboard,
|
||||
IconWhiteboardHide,
|
||||
IconWifi1Bar,
|
||||
IconWifi2Bars,
|
||||
IconWifi3Bars,
|
||||
IconYahoo
|
||||
};
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { IStateful } from '../app/types';
|
||||
import { ConnectionFailedError } from '../connection/actions.any';
|
||||
import { ConnectionFailedError } from '../connection/types';
|
||||
import { toState } from '../redux/functions';
|
||||
|
||||
import JitsiMeetJS from './_';
|
||||
|
||||
@@ -22,3 +22,4 @@ export const JitsiRecordingConstants = JitsiMeetJS.constants.recording;
|
||||
export const JitsiSIPVideoGWStatus = JitsiMeetJS.constants.sipVideoGW;
|
||||
export const JitsiTrackErrors = JitsiMeetJS.errors.track;
|
||||
export const JitsiTrackEvents = JitsiMeetJS.events.track;
|
||||
export const RTCStatsEvents = JitsiMeetJS.events.rtcstats;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { IStore } from '../../app/types';
|
||||
import RTCStats from '../../rtcstats/RTCStats';
|
||||
import { canSendRtcstatsData } from '../../rtcstats/functions';
|
||||
import { isRTCStatsEnabled } from '../../rtcstats/functions';
|
||||
import { getCurrentConference } from '../conference/functions';
|
||||
|
||||
/**
|
||||
@@ -60,7 +60,7 @@ export default class JitsiMeetLogStorage {
|
||||
// Saving the logs in RTCStats is a new feature and so there is no prior behavior that needs to be maintained.
|
||||
// That said, this is still experimental and needs to be rolled out gradually so we want this to be off by
|
||||
// default.
|
||||
return config?.analytics?.rtcstatsStoreLogs && canSendRtcstatsData(this.getState());
|
||||
return config?.analytics?.rtcstatsStoreLogs && isRTCStatsEnabled(this.getState());
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,10 +1,7 @@
|
||||
import React from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import {
|
||||
Button as NativePaperButton,
|
||||
Text,
|
||||
TouchableRipple
|
||||
} from 'react-native-paper';
|
||||
import { TouchableHighlight } from 'react-native';
|
||||
import { Button as NativePaperButton, Text } from 'react-native-paper';
|
||||
|
||||
import { BUTTON_MODES, BUTTON_TYPES } from '../../constants.native';
|
||||
import BaseTheme from '../BaseTheme.native';
|
||||
@@ -12,13 +9,13 @@ import { IButtonProps } from '../types';
|
||||
|
||||
import styles from './buttonStyles';
|
||||
|
||||
|
||||
export interface IProps extends IButtonProps {
|
||||
color?: string | undefined;
|
||||
contentStyle?: Object | undefined;
|
||||
labelStyle?: Object | undefined;
|
||||
mode?: any;
|
||||
style?: Object | undefined;
|
||||
useRippleColor?: boolean;
|
||||
}
|
||||
|
||||
const Button: React.FC<IProps> = ({
|
||||
@@ -32,16 +29,12 @@ const Button: React.FC<IProps> = ({
|
||||
mode = BUTTON_MODES.CONTAINED,
|
||||
onClick: onPress,
|
||||
style,
|
||||
type,
|
||||
useRippleColor = true
|
||||
type
|
||||
}: IProps) => {
|
||||
const { t } = useTranslation();
|
||||
const { DESTRUCTIVE, PRIMARY, SECONDARY, TERTIARY } = BUTTON_TYPES;
|
||||
const { CONTAINED, TEXT } = BUTTON_MODES;
|
||||
|
||||
const rippleColor
|
||||
= useRippleColor ? BaseTheme.palette.action03Active : 'transparent';
|
||||
|
||||
let buttonLabelStyles;
|
||||
let buttonStyles;
|
||||
let color;
|
||||
@@ -72,17 +65,16 @@ const Button: React.FC<IProps> = ({
|
||||
}
|
||||
|
||||
if (type === TERTIARY) {
|
||||
if (useRippleColor && disabled) {
|
||||
if (disabled) {
|
||||
buttonLabelStyles = styles.buttonLabelTertiaryDisabled;
|
||||
}
|
||||
buttonLabelStyles = styles.buttonLabelTertiary;
|
||||
|
||||
return (
|
||||
<TouchableRipple
|
||||
<TouchableHighlight
|
||||
accessibilityLabel = { accessibilityLabel }
|
||||
disabled = { disabled }
|
||||
onPress = { onPress }
|
||||
rippleColor = { rippleColor }
|
||||
style = { [
|
||||
buttonStyles,
|
||||
style
|
||||
@@ -92,7 +84,7 @@ const Button: React.FC<IProps> = ({
|
||||
buttonLabelStyles,
|
||||
labelStyle
|
||||
] }>{ t(labelKey ?? '') }</Text>
|
||||
</TouchableRipple>
|
||||
</TouchableHighlight>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import React from 'react';
|
||||
import { TouchableRipple } from 'react-native-paper';
|
||||
import { TouchableHighlight } from 'react-native';
|
||||
|
||||
import Icon from '../../../icons/components/Icon';
|
||||
import styles from '../../../react/components/native/styles';
|
||||
@@ -22,47 +22,47 @@ const IconButton: React.FC<IIconButtonProps> = ({
|
||||
const { PRIMARY, SECONDARY, TERTIARY } = BUTTON_TYPES;
|
||||
|
||||
let color;
|
||||
let rippleColor;
|
||||
let underlayColor;
|
||||
let iconButtonContainerStyles;
|
||||
|
||||
if (type === PRIMARY) {
|
||||
color = BaseTheme.palette.icon01;
|
||||
iconButtonContainerStyles = styles.iconButtonContainerPrimary;
|
||||
rippleColor = BaseTheme.palette.action01;
|
||||
underlayColor = BaseTheme.palette.action01;
|
||||
} else if (type === SECONDARY) {
|
||||
color = BaseTheme.palette.icon04;
|
||||
iconButtonContainerStyles = styles.iconButtonContainerSecondary;
|
||||
rippleColor = BaseTheme.palette.action02;
|
||||
underlayColor = BaseTheme.palette.action02;
|
||||
} else if (type === TERTIARY) {
|
||||
color = iconColor;
|
||||
iconButtonContainerStyles = styles.iconButtonContainer;
|
||||
rippleColor = BaseTheme.palette.action03;
|
||||
underlayColor = BaseTheme.palette.action03;
|
||||
} else {
|
||||
color = iconColor;
|
||||
rippleColor = tapColor;
|
||||
underlayColor = tapColor;
|
||||
}
|
||||
|
||||
if (disabled) {
|
||||
color = BaseTheme.palette.icon03;
|
||||
iconButtonContainerStyles = styles.iconButtonContainerDisabled;
|
||||
rippleColor = 'transparent';
|
||||
underlayColor = 'transparent';
|
||||
}
|
||||
|
||||
return (
|
||||
<TouchableRipple
|
||||
<TouchableHighlight
|
||||
accessibilityLabel = { accessibilityLabel }
|
||||
disabled = { disabled }
|
||||
onPress = { onPress }
|
||||
rippleColor = { rippleColor }
|
||||
style = { [
|
||||
iconButtonContainerStyles,
|
||||
style
|
||||
] }>
|
||||
] }
|
||||
underlayColor = { underlayColor }>
|
||||
<Icon
|
||||
color = { color }
|
||||
size = { 20 || size }
|
||||
src = { src } />
|
||||
</TouchableRipple>
|
||||
</TouchableHighlight>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -559,7 +559,7 @@ export function urlObjectToString(o: { [key: string]: any; }): string | undefine
|
||||
|
||||
let { hash } = url;
|
||||
|
||||
for (const urlPrefix of [ 'config', 'interfaceConfig', 'devices', 'userInfo', 'appData' ]) {
|
||||
for (const urlPrefix of [ 'config', 'iceServers', 'interfaceConfig', 'devices', 'userInfo', 'appData' ]) {
|
||||
const urlParamsArray
|
||||
= _objectToURLParamsArray(
|
||||
o[`${urlPrefix}Overwrite`]
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -36,11 +36,8 @@ import { FILMSTRIP_SIZE } from '../../../filmstrip/constants';
|
||||
import { isFilmstripVisible } from '../../../filmstrip/functions.native';
|
||||
import CalleeInfoContainer from '../../../invite/components/callee-info/CalleeInfoContainer';
|
||||
import LargeVideo from '../../../large-video/components/LargeVideo.native';
|
||||
import { startKnocking } from '../../../lobby/actions.any';
|
||||
import { getIsLobbyVisible } from '../../../lobby/functions';
|
||||
import { navigate }
|
||||
from '../../../mobile/navigation/components/conference/ConferenceNavigationContainerRef';
|
||||
import { shouldEnableAutoKnock } from '../../../mobile/navigation/functions';
|
||||
import { navigate } from '../../../mobile/navigation/components/conference/ConferenceNavigationContainerRef';
|
||||
import { screen } from '../../../mobile/navigation/routes';
|
||||
import { setPictureInPictureEnabled } from '../../../mobile/picture-in-picture/functions';
|
||||
import Captions from '../../../subtitles/components/native/Captions';
|
||||
@@ -135,11 +132,6 @@ interface IProps extends AbstractProps {
|
||||
*/
|
||||
_reducedUI: boolean;
|
||||
|
||||
/**
|
||||
* Indicates if we should auto-knock.
|
||||
*/
|
||||
_shouldEnableAutoKnock: boolean;
|
||||
|
||||
/**
|
||||
* Indicates whether the lobby screen should be visible.
|
||||
*/
|
||||
@@ -238,17 +230,11 @@ class Conference extends AbstractConference<IProps, State> {
|
||||
*/
|
||||
componentDidUpdate(prevProps: IProps) {
|
||||
const {
|
||||
_shouldEnableAutoKnock,
|
||||
_showLobby,
|
||||
dispatch
|
||||
_showLobby
|
||||
} = this.props;
|
||||
|
||||
if (!prevProps._showLobby && _showLobby) {
|
||||
navigate(screen.lobby.root);
|
||||
|
||||
if (_shouldEnableAutoKnock) {
|
||||
dispatch(startKnocking());
|
||||
}
|
||||
}
|
||||
|
||||
if (prevProps._showLobby && !_showLobby) {
|
||||
@@ -600,7 +586,6 @@ function _mapStateToProps(state: IReduxState, _ownProps: any) {
|
||||
_largeVideoParticipantId: state['features/large-video'].participantId,
|
||||
_pictureInPictureEnabled: getFeatureFlag(state, PIP_ENABLED),
|
||||
_reducedUI: reducedUI,
|
||||
_shouldEnableAutoKnock: shouldEnableAutoKnock(state),
|
||||
_showLobby: getIsLobbyVisible(state),
|
||||
_startCarMode: startCarMode,
|
||||
_toolboxVisible: isToolboxVisible(state)
|
||||
|
||||
@@ -15,7 +15,6 @@ import Chat from '../../../chat/components/web/Chat';
|
||||
import MainFilmstrip from '../../../filmstrip/components/web/MainFilmstrip';
|
||||
import ScreenshareFilmstrip from '../../../filmstrip/components/web/ScreenshareFilmstrip';
|
||||
import StageFilmstrip from '../../../filmstrip/components/web/StageFilmstrip';
|
||||
import { isFilmstripDisabled } from '../../../filmstrip/functions.web';
|
||||
import CalleeInfoContainer from '../../../invite/components/callee-info/CalleeInfoContainer';
|
||||
import LargeVideo from '../../../large-video/components/LargeVideo.web';
|
||||
import LobbyScreen from '../../../lobby/components/web/LobbyScreen';
|
||||
@@ -64,11 +63,6 @@ interface IProps extends AbstractProps, WithTranslation {
|
||||
*/
|
||||
_backgroundAlpha?: number;
|
||||
|
||||
/**
|
||||
* Whether the vertical/horizonal filmstrip is disabled.
|
||||
*/
|
||||
_filmstripDisabled: boolean;
|
||||
|
||||
/**
|
||||
* Are any overlays visible?
|
||||
*/
|
||||
@@ -205,14 +199,12 @@ class Conference extends AbstractConference<IProps, any> {
|
||||
*/
|
||||
render() {
|
||||
const {
|
||||
_filmstripDisabled,
|
||||
_isAnyOverlayVisible,
|
||||
_layoutClassName,
|
||||
_notificationsVisible,
|
||||
_overflowDrawer,
|
||||
_showLobby,
|
||||
_showPrejoin,
|
||||
_shouldDisplayTileView,
|
||||
t
|
||||
} = this.props;
|
||||
|
||||
@@ -238,7 +230,7 @@ class Conference extends AbstractConference<IProps, any> {
|
||||
_showPrejoin || _showLobby || (<>
|
||||
<StageFilmstrip />
|
||||
<ScreenshareFilmstrip />
|
||||
{ (!_filmstripDisabled || _shouldDisplayTileView) && <MainFilmstrip /> }
|
||||
<MainFilmstrip />
|
||||
</>)
|
||||
}
|
||||
</div>
|
||||
@@ -404,7 +396,6 @@ function _mapStateToProps(state: IReduxState) {
|
||||
return {
|
||||
...abstractMapStateToProps(state),
|
||||
_backgroundAlpha: backgroundAlpha,
|
||||
_filmstripDisabled: isFilmstripDisabled(state),
|
||||
_isAnyOverlayVisible: Boolean(getOverlayToRender(state)),
|
||||
_layoutClassName: LAYOUT_CLASSNAMES[getCurrentLayout(state) ?? ''],
|
||||
_mouseMoveCallbackInterval: mouseMoveCallbackInterval,
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -79,10 +79,8 @@ export function submitVideoDeviceSelectionTab(newState: any, isDisplayedOnWelcom
|
||||
userSelectedCameraDeviceLabel:
|
||||
getDeviceLabelById(getState(), newState.selectedVideoInputId, 'videoInput')
|
||||
}));
|
||||
|
||||
dispatch(setVideoInputDevice(newState.selectedVideoInputId));
|
||||
}
|
||||
|
||||
if (newState.localFlipX !== currentState.localFlipX) {
|
||||
dispatch(updateSettings({
|
||||
localFlipX: newState.localFlipX
|
||||
|
||||
@@ -14,7 +14,7 @@ import {
|
||||
getDeviceIdByLabel,
|
||||
groupDevicesByKind
|
||||
} from '../base/devices/functions.web';
|
||||
import { isIosMobileBrowser } from '../base/environment/utils';
|
||||
import { isIosMobileBrowser, isMobileBrowser } from '../base/environment/utils';
|
||||
import JitsiMeetJS from '../base/lib-jitsi-meet';
|
||||
import { toState } from '../base/redux/functions';
|
||||
import {
|
||||
@@ -97,7 +97,7 @@ export function getAudioDeviceSelectionDialogProps(stateful: IStateful, isDispla
|
||||
export function getVideoDeviceSelectionDialogProps(stateful: IStateful, isDisplayedOnWelcomePage: boolean) {
|
||||
// On mobile Safari because of https://bugs.webkit.org/show_bug.cgi?id=179363#c30, the old track is stopped
|
||||
// by the browser when a new track is created for preview. That's why we are disabling all previews.
|
||||
const disablePreviews = isIosMobileBrowser();
|
||||
const disablePreviews = isMobileBrowser();
|
||||
|
||||
const state = toState(stateful);
|
||||
const settings = state['features/base/settings'];
|
||||
|
||||
@@ -2,6 +2,7 @@ import { IReduxState } from '../app/types';
|
||||
import { IStateful } from '../base/app/types';
|
||||
import { toState } from '../base/redux/functions';
|
||||
|
||||
import logger from './logger';
|
||||
|
||||
/**
|
||||
* Extracts the fqn part from a path, where fqn represents
|
||||
@@ -64,3 +65,31 @@ export async function getDynamicBrandingUrl(stateful: IStateful) {
|
||||
export function isDynamicBrandingDataLoaded(state: IReduxState) {
|
||||
return state['features/dynamic-branding'].customizationReady;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch SVG XMLs from branding icons urls.
|
||||
*
|
||||
* @param {Object} customIcons - The map of branded icons.
|
||||
* @returns {Object}
|
||||
*/
|
||||
export const fetchCustomIcons = async (customIcons: Record<string, string>) => {
|
||||
const localCustomIcons: Record<string, string> = {};
|
||||
|
||||
for (const [ key, url ] of Object.entries(customIcons)) {
|
||||
try {
|
||||
const response = await fetch(url);
|
||||
|
||||
if (response.ok) {
|
||||
const svgXml = await response.text();
|
||||
|
||||
localCustomIcons[key] = svgXml;
|
||||
} else {
|
||||
logger.error(`Failed to fetch ${url}. Status: ${response.status}`);
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error(`Error fetching ${url}:`, error);
|
||||
}
|
||||
}
|
||||
|
||||
return localCustomIcons;
|
||||
};
|
||||
|
||||
29
react/features/dynamic-branding/middleware.any.ts
Normal file
29
react/features/dynamic-branding/middleware.any.ts
Normal file
@@ -0,0 +1,29 @@
|
||||
import MiddlewareRegistry from '../base/redux/MiddlewareRegistry';
|
||||
|
||||
import { SET_DYNAMIC_BRANDING_DATA } from './actionTypes';
|
||||
import { fetchCustomIcons } from './functions.any';
|
||||
import logger from './logger';
|
||||
|
||||
MiddlewareRegistry.register(() => next => action => {
|
||||
switch (action.type) {
|
||||
case SET_DYNAMIC_BRANDING_DATA: {
|
||||
const { customIcons } = action.value;
|
||||
|
||||
if (customIcons) {
|
||||
fetchCustomIcons(customIcons)
|
||||
.then(localCustomIcons => {
|
||||
action.value.brandedIcons = localCustomIcons;
|
||||
|
||||
return next(action);
|
||||
})
|
||||
.catch((error: any) => {
|
||||
logger.error('Error fetching branded custom icons:', error);
|
||||
});
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return next(action);
|
||||
});
|
||||
@@ -4,6 +4,7 @@ import MiddlewareRegistry from '../base/redux/MiddlewareRegistry';
|
||||
import { SET_DYNAMIC_BRANDING_DATA } from './actionTypes';
|
||||
import { fetchCustomBrandingData } from './actions.native';
|
||||
|
||||
import './middleware.any';
|
||||
|
||||
MiddlewareRegistry.register(store => next => action => {
|
||||
switch (action.type) {
|
||||
@@ -20,6 +21,7 @@ MiddlewareRegistry.register(store => next => action => {
|
||||
avatarBackgrounds = [],
|
||||
backgroundColor,
|
||||
backgroundImageUrl,
|
||||
brandedIcons,
|
||||
didPageUrl,
|
||||
inviteDomain
|
||||
} = action.value;
|
||||
@@ -28,6 +30,7 @@ MiddlewareRegistry.register(store => next => action => {
|
||||
avatarBackgrounds,
|
||||
backgroundColor,
|
||||
backgroundImageUrl,
|
||||
brandedIcons,
|
||||
didPageUrl,
|
||||
inviteDomain
|
||||
};
|
||||
|
||||
@@ -5,6 +5,7 @@ import { SET_DYNAMIC_BRANDING_DATA } from './actionTypes';
|
||||
import { fetchCustomBrandingData } from './actions.any';
|
||||
import { createMuiBrandingTheme } from './functions.web';
|
||||
|
||||
import './middleware.any';
|
||||
|
||||
MiddlewareRegistry.register(store => next => action => {
|
||||
switch (action.type) {
|
||||
|
||||
@@ -146,6 +146,7 @@ export interface IDynamicBrandingState {
|
||||
avatarBackgrounds: string[];
|
||||
backgroundColor: string;
|
||||
backgroundImageUrl: string;
|
||||
brandedIcons?: Record<string, string>;
|
||||
customizationFailed: boolean;
|
||||
customizationReady: boolean;
|
||||
defaultBranding: boolean;
|
||||
@@ -171,6 +172,7 @@ ReducerRegistry.register<IDynamicBrandingState>(STORE_NAME, (state = DEFAULT_STA
|
||||
avatarBackgrounds,
|
||||
backgroundColor,
|
||||
backgroundImageUrl,
|
||||
brandedIcons,
|
||||
defaultBranding,
|
||||
didPageUrl,
|
||||
inviteDomain,
|
||||
@@ -187,6 +189,7 @@ ReducerRegistry.register<IDynamicBrandingState>(STORE_NAME, (state = DEFAULT_STA
|
||||
avatarBackgrounds,
|
||||
backgroundColor,
|
||||
backgroundImageUrl,
|
||||
brandedIcons,
|
||||
defaultBranding,
|
||||
didPageUrl,
|
||||
inviteDomain,
|
||||
|
||||
@@ -43,6 +43,7 @@ import {
|
||||
isStageFilmstripTopPanel,
|
||||
shouldRemoteVideosBeVisible
|
||||
} from '../../functions';
|
||||
import { isFilmstripDisabled } from '../../functions.web';
|
||||
|
||||
import AudioTracksContainer from './AudioTracksContainer';
|
||||
import Thumbnail from './Thumbnail';
|
||||
@@ -74,6 +75,11 @@ interface IProps extends WithTranslation {
|
||||
*/
|
||||
_disableSelfView: boolean;
|
||||
|
||||
/**
|
||||
* Whether vertical/horizontal filmstrip is disabled through config.
|
||||
*/
|
||||
_filmstripDisabled: boolean;
|
||||
|
||||
/**
|
||||
* The height of the filmstrip.
|
||||
*/
|
||||
@@ -333,6 +339,7 @@ class Filmstrip extends PureComponent <IProps, IState> {
|
||||
const {
|
||||
_currentLayout,
|
||||
_disableSelfView,
|
||||
_filmstripDisabled,
|
||||
_localScreenShareId,
|
||||
_mainFilmstripVisible,
|
||||
_resizableFilmstrip,
|
||||
@@ -381,7 +388,8 @@ class Filmstrip extends PureComponent <IProps, IState> {
|
||||
let toolbar = null;
|
||||
|
||||
if (!this.props._iAmRecorder && this.props._isFilmstripButtonEnabled
|
||||
&& _currentLayout !== LAYOUTS.TILE_VIEW && (filmstripType === FILMSTRIP_TYPE.MAIN
|
||||
&& _currentLayout !== LAYOUTS.TILE_VIEW
|
||||
&& ((filmstripType === FILMSTRIP_TYPE.MAIN && !_filmstripDisabled)
|
||||
|| (filmstripType === FILMSTRIP_TYPE.STAGE && _topPanelFilmstrip))) {
|
||||
toolbar = this._renderToggleButton();
|
||||
}
|
||||
@@ -885,6 +893,7 @@ function _mapStateToProps(state: IReduxState, ownProps: any) {
|
||||
const { isOpen: shiftRight } = state['features/chat'];
|
||||
const disableSelfView = getHideSelfView(state);
|
||||
const { clientWidth, clientHeight } = state['features/base/responsive-ui'];
|
||||
const filmstripDisabled = isFilmstripDisabled(state);
|
||||
|
||||
const collapseTileView = reduceHeight
|
||||
&& isMobileBrowser()
|
||||
@@ -893,7 +902,8 @@ function _mapStateToProps(state: IReduxState, ownProps: any) {
|
||||
const shouldReduceHeight = reduceHeight && isMobileBrowser();
|
||||
const _topPanelVisible = isStageFilmstripTopPanel(state) && topPanelVisible;
|
||||
|
||||
let isVisible = visible || filmstripType !== FILMSTRIP_TYPE.MAIN;
|
||||
const notDisabled = visible && !filmstripDisabled;
|
||||
let isVisible = notDisabled || filmstripType !== FILMSTRIP_TYPE.MAIN;
|
||||
|
||||
if (_topPanelFilmstrip) {
|
||||
isVisible = _topPanelVisible;
|
||||
@@ -912,13 +922,14 @@ function _mapStateToProps(state: IReduxState, ownProps: any) {
|
||||
_chatOpen: state['features/chat'].isOpen,
|
||||
_currentLayout,
|
||||
_disableSelfView: disableSelfView,
|
||||
_filmstripDisabled: filmstripDisabled,
|
||||
_hasScroll,
|
||||
_iAmRecorder: Boolean(iAmRecorder),
|
||||
_isFilmstripButtonEnabled: isButtonEnabled('filmstrip', state),
|
||||
_isToolboxVisible: isToolboxVisible(state),
|
||||
_isVerticalFilmstrip,
|
||||
_localScreenShareId: localScreenShare?.id,
|
||||
_mainFilmstripVisible: visible,
|
||||
_mainFilmstripVisible: notDisabled,
|
||||
_maxFilmstripWidth: clientWidth - MIN_STAGE_VIEW_WIDTH,
|
||||
_maxTopPanelHeight: clientHeight - MIN_STAGE_VIEW_HEIGHT,
|
||||
_remoteParticipantsLength: _remoteParticipants?.length ?? 0,
|
||||
|
||||
@@ -116,6 +116,6 @@ export default {
|
||||
},
|
||||
|
||||
sendBtn: {
|
||||
marginRight: BaseTheme.spacing[2]
|
||||
marginRight: BaseTheme.spacing[3]
|
||||
}
|
||||
};
|
||||
|
||||
@@ -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 = [
|
||||
|
||||
22
react/features/mobile/full-screen/functions.ts
Normal file
22
react/features/mobile/full-screen/functions.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
import { IReduxState } from '../../app/types';
|
||||
import { getCurrentConference } from '../../base/conference/functions';
|
||||
import { isAnyDialogOpen } from '../../base/dialog/functions';
|
||||
import { FULLSCREEN_ENABLED } from '../../base/flags/constants';
|
||||
import { getFeatureFlag } from '../../base/flags/functions';
|
||||
import { isLocalVideoTrackDesktop } from '../../base/tracks/functions.any';
|
||||
|
||||
/**
|
||||
* Checks whether full-screen state should be used or not.
|
||||
*
|
||||
* @param {IReduxState} state - The redux state.
|
||||
* @returns {boolean} - Whether full-screen state shuld be used or not.
|
||||
*/
|
||||
export function shouldUseFullScreen(state: IReduxState) {
|
||||
const { enabled: audioOnly } = state['features/base/audio-only'];
|
||||
const conference = getCurrentConference(state);
|
||||
const dialogOpen = isAnyDialogOpen(state);
|
||||
const fullscreenEnabled = getFeatureFlag(state, FULLSCREEN_ENABLED, true);
|
||||
const isDesktopSharing = isLocalVideoTrackDesktop(state);
|
||||
|
||||
return conference ? !audioOnly && !dialogOpen && !isDesktopSharing && fullscreenEnabled : false;
|
||||
}
|
||||
@@ -2,14 +2,11 @@ import ImmersiveMode from 'react-native-immersive-mode';
|
||||
|
||||
import { IStore } from '../../app/types';
|
||||
import { APP_WILL_MOUNT, APP_WILL_UNMOUNT } from '../../base/app/actionTypes';
|
||||
import { getCurrentConference } from '../../base/conference/functions';
|
||||
import { isAnyDialogOpen } from '../../base/dialog/functions';
|
||||
import { FULLSCREEN_ENABLED } from '../../base/flags/constants';
|
||||
import { getFeatureFlag } from '../../base/flags/functions';
|
||||
import MiddlewareRegistry from '../../base/redux/MiddlewareRegistry';
|
||||
import StateListenerRegistry from '../../base/redux/StateListenerRegistry';
|
||||
|
||||
import { _setImmersiveSubscription } from './actions';
|
||||
import { shouldUseFullScreen } from './functions';
|
||||
import logger from './logger';
|
||||
|
||||
type BarVisibilityType = {
|
||||
@@ -48,14 +45,7 @@ MiddlewareRegistry.register(store => next => action => {
|
||||
});
|
||||
|
||||
StateListenerRegistry.register(
|
||||
/* selector */ state => {
|
||||
const { enabled: audioOnly } = state['features/base/audio-only'];
|
||||
const conference = getCurrentConference(state);
|
||||
const dialogOpen = isAnyDialogOpen(state);
|
||||
const fullscreenEnabled = getFeatureFlag(state, FULLSCREEN_ENABLED, true);
|
||||
|
||||
return conference ? !audioOnly && !dialogOpen && fullscreenEnabled : false;
|
||||
},
|
||||
/* selector */ shouldUseFullScreen,
|
||||
/* listener */ fullScreen => _setFullScreen(fullScreen)
|
||||
);
|
||||
|
||||
@@ -73,13 +63,7 @@ function _onImmersiveChange({ getState }: IStore) {
|
||||
const { appState } = state['features/background'];
|
||||
|
||||
if (appState === 'active') {
|
||||
const { enabled: audioOnly } = state['features/base/audio-only'];
|
||||
const conference = getCurrentConference(state);
|
||||
const dialogOpen = isAnyDialogOpen(state);
|
||||
const fullscreenEnabled = getFeatureFlag(state, FULLSCREEN_ENABLED, true);
|
||||
const fullScreen = conference ? !audioOnly && !dialogOpen && fullscreenEnabled : false;
|
||||
|
||||
_setFullScreen(fullScreen);
|
||||
_setFullScreen(shouldUseFullScreen(state));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -74,7 +74,7 @@ const HeaderNavigationButton = ({ color, disabled, label, onPress, src, style, t
|
||||
size = { 24 }
|
||||
src = { src }
|
||||
style = { [
|
||||
navigationStyles.headerNavigationButton,
|
||||
navigationStyles.headerNavigationButtonIcon,
|
||||
style
|
||||
] } />
|
||||
) : (
|
||||
@@ -87,8 +87,7 @@ const HeaderNavigationButton = ({ color, disabled, label, onPress, src, style, t
|
||||
btnStyle,
|
||||
style
|
||||
] }
|
||||
type = { BUTTON_TYPES.TERTIARY }
|
||||
useRippleColor = { false } />
|
||||
type = { BUTTON_TYPES.TERTIARY } />
|
||||
)}
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -17,6 +17,12 @@ const headerNavigationButtonLabel = {
|
||||
lineHeight: BaseTheme.spacing[3]
|
||||
};
|
||||
|
||||
const headerNavigationButton = {
|
||||
borderRadius: BaseTheme.shape.borderRadius,
|
||||
height: BaseTheme.spacing[6],
|
||||
marginLeft: BaseTheme.spacing[3]
|
||||
};
|
||||
|
||||
/**
|
||||
* Styles of the navigation feature.
|
||||
*/
|
||||
@@ -43,7 +49,12 @@ export const navigationStyles = {
|
||||
},
|
||||
|
||||
headerNavigationButton: {
|
||||
marginLeft: BaseTheme.spacing[2]
|
||||
...headerNavigationButton
|
||||
},
|
||||
|
||||
headerNavigationButtonIcon: {
|
||||
...headerNavigationButton,
|
||||
padding: BaseTheme.spacing[2]
|
||||
},
|
||||
|
||||
headerNavigationButtonDisabled: {
|
||||
|
||||
@@ -30,6 +30,33 @@ import MeetingParticipants from './MeetingParticipants';
|
||||
|
||||
const useStyles = makeStyles()(theme => {
|
||||
return {
|
||||
participantsPane: {
|
||||
backgroundColor: theme.palette.ui01,
|
||||
flexShrink: 0,
|
||||
overflow: 'hidden',
|
||||
position: 'relative',
|
||||
transition: 'width .16s ease-in-out',
|
||||
width: '315px',
|
||||
zIndex: 0,
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
fontWeight: 600,
|
||||
height: '100%',
|
||||
|
||||
[[ '& > *:first-child', '& > *:last-child' ] as any]: {
|
||||
flexShrink: 0
|
||||
},
|
||||
|
||||
'@media (max-width: 580px)': {
|
||||
height: '100dvh',
|
||||
position: 'fixed',
|
||||
left: 0,
|
||||
right: 0,
|
||||
top: 0,
|
||||
width: '100%'
|
||||
}
|
||||
},
|
||||
|
||||
container: {
|
||||
boxSizing: 'border-box',
|
||||
flex: 1,
|
||||
@@ -87,7 +114,7 @@ const useStyles = makeStyles()(theme => {
|
||||
});
|
||||
|
||||
const ParticipantsPane = () => {
|
||||
const { classes } = useStyles();
|
||||
const { classes, cx } = useStyles();
|
||||
const paneOpen = useSelector(getParticipantsPaneOpen);
|
||||
const isBreakoutRoomsSupported = useSelector((state: IReduxState) => state['features/base/conference'])
|
||||
.conference?.getBreakoutRooms()?.isSupported();
|
||||
@@ -136,49 +163,47 @@ const ParticipantsPane = () => {
|
||||
}
|
||||
|
||||
return (
|
||||
<div className = 'participants_pane'>
|
||||
<div className = 'participants_pane-content'>
|
||||
<div className = { classes.header }>
|
||||
<ClickableIcon
|
||||
accessibilityLabel = { t('participantsPane.close', 'Close') }
|
||||
icon = { IconCloseLarge }
|
||||
onClick = { onClosePane } />
|
||||
</div>
|
||||
<div className = { classes.container }>
|
||||
<LobbyParticipants />
|
||||
<br className = { classes.antiCollapse } />
|
||||
<MeetingParticipants
|
||||
searchString = { searchString }
|
||||
setSearchString = { setSearchString } />
|
||||
{isBreakoutRoomsSupported && <RoomList searchString = { searchString } />}
|
||||
{showAddRoomButton && <AddBreakoutRoomButton />}
|
||||
</div>
|
||||
{showFooter && (
|
||||
<div className = { classes.footer }>
|
||||
{showMuteAllButton && (
|
||||
<Button
|
||||
accessibilityLabel = { t('participantsPane.actions.muteAll') }
|
||||
labelKey = { 'participantsPane.actions.muteAll' }
|
||||
onClick = { onMuteAll }
|
||||
type = { BUTTON_TYPES.SECONDARY } />
|
||||
)}
|
||||
{showMoreActionsButton && (
|
||||
<div className = { classes.footerMoreContainer }>
|
||||
<Button
|
||||
accessibilityLabel = { t('participantsPane.actions.moreModerationActions') }
|
||||
icon = { IconDotsHorizontal }
|
||||
id = 'participants-pane-context-menu'
|
||||
onClick = { onToggleContext }
|
||||
type = { BUTTON_TYPES.SECONDARY } />
|
||||
<FooterContextMenu
|
||||
isOpen = { contextOpen }
|
||||
onDrawerClose = { onDrawerClose }
|
||||
onMouseLeave = { onToggleContext } />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
<div className = { cx('participants_pane', classes.participantsPane) }>
|
||||
<div className = { classes.header }>
|
||||
<ClickableIcon
|
||||
accessibilityLabel = { t('participantsPane.close', 'Close') }
|
||||
icon = { IconCloseLarge }
|
||||
onClick = { onClosePane } />
|
||||
</div>
|
||||
<div className = { classes.container }>
|
||||
<LobbyParticipants />
|
||||
<br className = { classes.antiCollapse } />
|
||||
<MeetingParticipants
|
||||
searchString = { searchString }
|
||||
setSearchString = { setSearchString } />
|
||||
{isBreakoutRoomsSupported && <RoomList searchString = { searchString } />}
|
||||
{showAddRoomButton && <AddBreakoutRoomButton />}
|
||||
</div>
|
||||
{showFooter && (
|
||||
<div className = { classes.footer }>
|
||||
{showMuteAllButton && (
|
||||
<Button
|
||||
accessibilityLabel = { t('participantsPane.actions.muteAll') }
|
||||
labelKey = { 'participantsPane.actions.muteAll' }
|
||||
onClick = { onMuteAll }
|
||||
type = { BUTTON_TYPES.SECONDARY } />
|
||||
)}
|
||||
{showMoreActionsButton && (
|
||||
<div className = { classes.footerMoreContainer }>
|
||||
<Button
|
||||
accessibilityLabel = { t('participantsPane.actions.moreModerationActions') }
|
||||
icon = { IconDotsHorizontal }
|
||||
id = 'participants-pane-context-menu'
|
||||
onClick = { onToggleContext }
|
||||
type = { BUTTON_TYPES.SECONDARY } />
|
||||
<FooterContextMenu
|
||||
isOpen = { contextOpen }
|
||||
onDrawerClose = { onDrawerClose }
|
||||
onMouseLeave = { onToggleContext } />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -40,6 +40,7 @@ import { IPrejoinProps } from '../../types';
|
||||
|
||||
import { preJoinStyles as styles } from './styles';
|
||||
|
||||
|
||||
const Prejoin: React.FC<IPrejoinProps> = ({ navigation }: IPrejoinProps) => {
|
||||
const dispatch = useDispatch();
|
||||
const isFocused = useIsFocused();
|
||||
@@ -83,6 +84,16 @@ const Prejoin: React.FC<IPrejoinProps> = ({ navigation }: IPrejoinProps) => {
|
||||
return true;
|
||||
}, [ dispatch ]);
|
||||
|
||||
const { PRIMARY, TERTIARY } = BUTTON_TYPES;
|
||||
const joinButtonDisabled = !displayName && isDisplayNameMandatory;
|
||||
|
||||
useEffect(() => {
|
||||
BackHandler.addEventListener('hardwareBackPress', goBack);
|
||||
|
||||
return () => BackHandler.removeEventListener('hardwareBackPress', goBack);
|
||||
|
||||
}, []);
|
||||
|
||||
const headerLeft = () => {
|
||||
if (Platform.OS === 'ios') {
|
||||
return (
|
||||
@@ -99,16 +110,6 @@ const Prejoin: React.FC<IPrejoinProps> = ({ navigation }: IPrejoinProps) => {
|
||||
);
|
||||
};
|
||||
|
||||
const { PRIMARY, TERTIARY } = BUTTON_TYPES;
|
||||
const joinButtonDisabled = !displayName && isDisplayNameMandatory;
|
||||
|
||||
useEffect(() => {
|
||||
BackHandler.addEventListener('hardwareBackPress', goBack);
|
||||
|
||||
return () => BackHandler.removeEventListener('hardwareBackPress', goBack);
|
||||
|
||||
}, []);
|
||||
|
||||
useLayoutEffect(() => {
|
||||
navigation.setOptions({
|
||||
headerLeft,
|
||||
|
||||
@@ -1,6 +1,13 @@
|
||||
import React, { useCallback, useLayoutEffect } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Platform, 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';
|
||||
@@ -20,6 +27,7 @@ import { IPrejoinProps } from '../../types';
|
||||
|
||||
import { preJoinStyles as styles } from './styles';
|
||||
|
||||
|
||||
const UnsafeRoomWarning: React.FC<IPrejoinProps> = ({ navigation }: IPrejoinProps) => {
|
||||
const dispatch = useDispatch();
|
||||
const { t } = useTranslation();
|
||||
|
||||
@@ -5,166 +5,40 @@ import {
|
||||
PC_STATE_FAILED
|
||||
// @ts-expect-error
|
||||
} from '@jitsi/rtcstats/events';
|
||||
// @ts-expect-error
|
||||
import rtcstatsInit from '@jitsi/rtcstats/rtcstats';
|
||||
// @ts-expect-error
|
||||
import traceInit from '@jitsi/rtcstats/trace-ws';
|
||||
/* eslint-enable lines-around-comment */
|
||||
|
||||
import { createRTCStatsTraceCloseEvent } from '../analytics/AnalyticsEvents';
|
||||
import { sendAnalytics } from '../analytics/functions';
|
||||
import JitsiMeetJS, { RTCStatsEvents } from '../base/lib-jitsi-meet';
|
||||
|
||||
import logger from './logger';
|
||||
import {
|
||||
DominantSpeakerData,
|
||||
E2ERTTData,
|
||||
FaceLandmarksData,
|
||||
InitOptions,
|
||||
VideoTypeData
|
||||
} from './types';
|
||||
|
||||
/**
|
||||
* Filter out RTCPeerConnection that are created by callstats.io.
|
||||
*
|
||||
* @param {*} config - Config object sent to the PC c'tor.
|
||||
* @returns {boolean}
|
||||
*/
|
||||
function connectionFilter(config: any) {
|
||||
if (config?.iceServers[0] && config.iceServers[0].urls) {
|
||||
for (const iceUrl of config.iceServers[0].urls) {
|
||||
if (iceUrl.indexOf('callstats.io') >= 0) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Class that controls the rtcstats flow, because it overwrites and proxies global function it should only be
|
||||
* initialized once.
|
||||
* Handle lib-jitsi-meet rtcstats events and send jitsi-meet specific statistics.
|
||||
*/
|
||||
class RTCStats {
|
||||
trace: any;
|
||||
options?: InitOptions;
|
||||
isPeerConnectionWrapped = false;
|
||||
connStateEvents: any = [];
|
||||
private _connStateEvents: Array<any> = [];
|
||||
private _initialized = false;
|
||||
|
||||
/**
|
||||
* Initialize the rtcstats components, if the options have changed.
|
||||
* Handles rtcstats events.
|
||||
*
|
||||
* @param {Oject} newOptions -.
|
||||
* @returns {void}
|
||||
*/
|
||||
maybeInit(newOptions: InitOptions) {
|
||||
const oldOptions = this.options;
|
||||
const changed = !oldOptions || (oldOptions.endpoint !== newOptions.endpoint
|
||||
|| oldOptions.meetingFqn !== newOptions.meetingFqn
|
||||
|| oldOptions.pollInterval !== newOptions.pollInterval
|
||||
|| oldOptions.sendSdp !== newOptions.sendSdp
|
||||
|| oldOptions.useLegacy !== newOptions.useLegacy);
|
||||
init() {
|
||||
this._connStateEvents = [];
|
||||
|
||||
if (changed) {
|
||||
this.reset();
|
||||
|
||||
if (newOptions.meetingFqn && newOptions.endpoint) {
|
||||
this.init(newOptions);
|
||||
} else {
|
||||
logger.warn('RTCStats is enabled but it has not been configured.');
|
||||
}
|
||||
if (!this._initialized) {
|
||||
JitsiMeetJS.rtcstats.on(
|
||||
RTCStatsEvents.RTC_STATS_PC_EVENT,
|
||||
(pcEvent: any) => this.handleRTCStatsEvent(pcEvent));
|
||||
this._initialized = true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrapper method for the underlying trace object to be used as a static reference from inside the wrapped
|
||||
* PeerConnection.
|
||||
*
|
||||
* @param {any[]} data - The stats entry to send to the rtcstats endpoint.
|
||||
* @returns {void}
|
||||
*/
|
||||
statsEntry(...data: any[]) {
|
||||
this.trace?.statsEntry(...data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the rtcstats components. First off we initialize the trace, which is a wrapped websocket
|
||||
* that does the actual communication with the server. Secondly, the rtcstats component is initialized,
|
||||
* it overwrites GUM and PeerConnection global functions and adds a proxy over them used to capture stats.
|
||||
* Note, lib-jitsi-meet takes references to these methods before initializing so the init method needs to be
|
||||
* loaded before it does.
|
||||
*
|
||||
* @param {Object} options -.
|
||||
* @param {string} options.endpoint - The Amplitude app key required.
|
||||
* @param {string} options.meetingFqn - The meeting fqn.
|
||||
* @param {boolean} options.useLegacy - Switch to legacy chrome webrtc statistics. Parameter will only have
|
||||
* an effect on chrome based applications.
|
||||
* @param {number} options.pollInterval - The getstats poll interval in ms.
|
||||
* @param {boolean} options.sendSdp - Determines if the client sends SDP to the rtcstats server.
|
||||
* @returns {void}
|
||||
*/
|
||||
init(options: InitOptions) {
|
||||
|
||||
const { endpoint, meetingFqn, useLegacy, pollInterval, sendSdp } = options;
|
||||
|
||||
const traceOptions = {
|
||||
endpoint,
|
||||
meetingFqn,
|
||||
onCloseCallback: this.handleTraceWSClose.bind(this),
|
||||
useLegacy
|
||||
};
|
||||
|
||||
this.trace = traceInit(traceOptions);
|
||||
if (!this.isPeerConnectionWrapped) {
|
||||
const rtcstatsOptions = {
|
||||
connectionFilter,
|
||||
pollInterval,
|
||||
useLegacy,
|
||||
sendSdp,
|
||||
eventCallback: this.handleRtcstatsEvent.bind(this)
|
||||
};
|
||||
|
||||
const statsEntryCallback = { statsEntry: this.statsEntry.bind(this) };
|
||||
|
||||
rtcstatsInit(statsEntryCallback, rtcstatsOptions);
|
||||
this.isPeerConnectionWrapped = true;
|
||||
}
|
||||
this.options = options;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether or not the RTCStats is initialized.
|
||||
*
|
||||
* @returns {boolean}
|
||||
*/
|
||||
isInitialized() {
|
||||
return this.options !== undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets the rtcstats.
|
||||
*
|
||||
* @returns {void}
|
||||
*/
|
||||
reset() {
|
||||
delete this.options;
|
||||
|
||||
if (this.trace) {
|
||||
this.trace.close();
|
||||
delete this.trace;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Send identity data to rtcstats server, this will be reflected in the identity section of the stats dump.
|
||||
* It can be generally used to send additional metadata that might be relevant such as amplitude user data
|
||||
* or deployment specific information.
|
||||
*
|
||||
* @param {any} identityData - Metadata object to send as identity.
|
||||
* @returns {void}
|
||||
*/
|
||||
sendIdentityData(identityData: any) {
|
||||
this.trace?.identity('identity', null, identityData);
|
||||
}
|
||||
|
||||
/**
|
||||
* Send console logs to rtcstats server.
|
||||
*
|
||||
@@ -172,7 +46,7 @@ class RTCStats {
|
||||
* @returns {void}
|
||||
*/
|
||||
sendLogs(logEntries: Array<string | any>) {
|
||||
this.trace?.statsEntry('logs', null, logEntries);
|
||||
JitsiMeetJS.rtcstats.sendStatsEntry('logs', logEntries);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -182,7 +56,7 @@ class RTCStats {
|
||||
* @returns {void}
|
||||
*/
|
||||
sendDominantSpeakerData(dominantSpeakerData: DominantSpeakerData) {
|
||||
this.trace?.statsEntry('dominantSpeaker', null, dominantSpeakerData);
|
||||
JitsiMeetJS.rtcstats.sendStatsEntry('dominantSpeaker', dominantSpeakerData);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -191,8 +65,8 @@ class RTCStats {
|
||||
* @param {Object} e2eRttData - The object that holds the e2e data.
|
||||
* @returns {void}
|
||||
*/
|
||||
sendE2eRttData(e2eRttData: E2ERTTData) {
|
||||
this.trace?.statsEntry('e2eRtt', null, e2eRttData);
|
||||
sendE2ERTTData(e2eRttData: E2ERTTData) {
|
||||
JitsiMeetJS.rtcstats.sendStatsEntry('e2eRtt', e2eRttData);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -203,7 +77,7 @@ class RTCStats {
|
||||
* @returns {void}
|
||||
*/
|
||||
sendConferenceTimestamp(timestamp: number) {
|
||||
this.trace?.statsEntry('conferenceStartTimestamp', null, timestamp);
|
||||
JitsiMeetJS.rtcstats.sendStatsEntry('conferenceStartTimestamp', timestamp);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -213,7 +87,7 @@ class RTCStats {
|
||||
* @returns {void}
|
||||
*/
|
||||
sendVideoTypeData(videoTypeData: VideoTypeData) {
|
||||
this.trace?.statsEntry('setVideoType', null, videoTypeData);
|
||||
JitsiMeetJS.rtcstats.sendStatsEntry('setVideoType', videoTypeData);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -223,53 +97,32 @@ class RTCStats {
|
||||
* @returns {void}
|
||||
*/
|
||||
sendFaceLandmarksData(faceLandmarksData: FaceLandmarksData) {
|
||||
this.trace?.statsEntry('faceLandmarks', null, faceLandmarksData);
|
||||
}
|
||||
|
||||
/**
|
||||
* Connect to the rtcstats server instance. Stats (data obtained from getstats) won't be send until the
|
||||
* connect successfully initializes, however calls to GUM are recorded in an internal buffer even if not
|
||||
* connected and sent once it is established.
|
||||
*
|
||||
* @param {boolean} isBreakoutRoom - Flag indicating if the user is in a breakout room.
|
||||
* @returns {void}
|
||||
*/
|
||||
connect(isBreakoutRoom: boolean) {
|
||||
this.trace?.connect(isBreakoutRoom);
|
||||
}
|
||||
|
||||
/**
|
||||
* Self explanatory; closes the web socked connection.
|
||||
* Note, at the point of writing this documentation there was no method to reset the function overwrites,
|
||||
* thus even if the websocket is closed the global function proxies are still active but send no data,
|
||||
* this shouldn't influence the normal flow of the application.
|
||||
*
|
||||
* @returns {void}
|
||||
*/
|
||||
close() {
|
||||
this.trace?.close();
|
||||
JitsiMeetJS.rtcstats.sendStatsEntry('faceLandmarks', faceLandmarksData);
|
||||
}
|
||||
|
||||
/**
|
||||
* RTCStats client can notify the APP of any PeerConnection related event that occurs.
|
||||
*
|
||||
* @param {Object} event - Rtcstats event.
|
||||
* @param {Object} event - The PeerConnection event.
|
||||
* @param {string} event.type - The event type.
|
||||
* @param {Object} event.body - Event body.
|
||||
* @param {string} event.body.isP2P - PeerConnection type.
|
||||
* @param {string} event.body.state - PeerConnection state change which triggered the event.
|
||||
* @returns {void}
|
||||
*/
|
||||
handleRtcstatsEvent(event: any) {
|
||||
handleRTCStatsEvent(event: any) {
|
||||
switch (event.type) {
|
||||
case PC_CON_STATE_CHANGE: {
|
||||
const { body: { isP2P, state } = { state: null,
|
||||
isP2P: null } } = event;
|
||||
const { body: { isP2P = null, state = null } } = event;
|
||||
|
||||
this.connStateEvents.push(event.body);
|
||||
this._connStateEvents.push(event.body);
|
||||
|
||||
// We only report PC related connection issues. If the rtcstats websocket is not connected at this point
|
||||
// it usually means that none of our services can be reached i.e. there's problem with the internet
|
||||
// connection and not necessarily with reaching the JVB (due to a firewall or other reasons).
|
||||
if (state === PC_STATE_FAILED && this.trace.isConnected()) {
|
||||
if (state === PC_STATE_FAILED) {
|
||||
const connectionType = isP2P ? 'P2P' : 'JVB';
|
||||
const wasConnected = this.connStateEvents.some((connectionEvent: { isP2P: any; state: string; }) =>
|
||||
const wasConnected = this._connStateEvents.some((connectionEvent: { isP2P: any; state: string; }) =>
|
||||
(connectionEvent.isP2P === isP2P) && (connectionEvent.state === PC_STATE_CONNECTED));
|
||||
|
||||
logger.info(`${connectionType} PeerConnection failed, previously connected: ${wasConnected}`);
|
||||
@@ -283,21 +136,6 @@ class RTCStats {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The way rtcstats is currently designed the ws wouldn't normally be closed by the application logic but rather
|
||||
* by the page being closed/reloaded. Using this assumption any onclose event is most likely something abnormal
|
||||
* that happened on the ws. We then track this in order to determine how many rtcstats connection were closed
|
||||
* prematurely.
|
||||
*
|
||||
* @param {Object} closeEvent - Event sent by ws onclose.
|
||||
* @returns {void}
|
||||
*/
|
||||
handleTraceWSClose(closeEvent: any) {
|
||||
logger.info('RTCStats trace ws closed', closeEvent);
|
||||
|
||||
sendAnalytics(createRTCStatsTraceCloseEvent(closeEvent));
|
||||
}
|
||||
}
|
||||
|
||||
export default new RTCStats();
|
||||
|
||||
@@ -1,112 +1,29 @@
|
||||
// @ts-expect-error
|
||||
import { jitsiLocalStorage } from '@jitsi/js-utils';
|
||||
|
||||
import { getAmplitudeIdentity } from '../analytics/functions';
|
||||
import { IStore } from '../app/types';
|
||||
import { IStateful } from '../base/app/types';
|
||||
import { getAnalyticsRoomName, getConferenceOptions } from '../base/conference/functions';
|
||||
import { getLocalParticipant } from '../base/participants/functions';
|
||||
import { toState } from '../base/redux/functions';
|
||||
|
||||
import RTCStats from './RTCStats';
|
||||
import logger from './logger';
|
||||
|
||||
/**
|
||||
* Checks whether rtcstats is enabled or not.
|
||||
*
|
||||
* @param {IStateful} stateful - The redux store or {@code getState} function.
|
||||
* @returns {boolean}
|
||||
*/
|
||||
export function isRtcstatsEnabled(stateful: IStateful) {
|
||||
export function isRTCStatsEnabled(stateful: IStateful) {
|
||||
const state = toState(stateful);
|
||||
const { analytics } = state['features/base/config'];
|
||||
|
||||
return analytics?.rtcstatsEnabled ?? false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Can the rtcstats service send data.
|
||||
*
|
||||
* @param {IStateful} stateful - The redux store or {@code getState} function.
|
||||
* @returns {boolean}
|
||||
*/
|
||||
export function canSendRtcstatsData(stateful: IStateful) {
|
||||
return isRtcstatsEnabled(stateful) && RTCStats.isInitialized();
|
||||
}
|
||||
|
||||
type Identity = {
|
||||
isBreakoutRoom: boolean;
|
||||
|
||||
// Unique identifier for a conference session, not to be confused with meeting name
|
||||
// i.e. If all participants leave a meeting it will have a different value on the next join.
|
||||
meetingUniqueId?: string;
|
||||
roomId?: string;
|
||||
};
|
||||
|
||||
/**
|
||||
* Connects to the rtcstats service and sends the identity data.
|
||||
*
|
||||
* @param {IStore} store - Redux Store.
|
||||
* @param {Identity} identity - Identity data for the client.
|
||||
* @returns {void}
|
||||
*/
|
||||
export function connectAndSendIdentity({ getState, dispatch }: IStore, identity: Identity) {
|
||||
const state = getState();
|
||||
|
||||
if (canSendRtcstatsData(state)) {
|
||||
|
||||
// Once the conference started connect to the rtcstats server and send data.
|
||||
try {
|
||||
RTCStats.connect(identity.isBreakoutRoom);
|
||||
const localParticipant = getLocalParticipant(state);
|
||||
const options = getConferenceOptions(state);
|
||||
|
||||
// The current implementation of rtcstats-server is configured to send data to amplitude, thus
|
||||
// we add identity specific information so we can correlate on the amplitude side. If amplitude is
|
||||
// not configured an empty object will be sent.
|
||||
// The current configuration of the conference is also sent as metadata to rtcstats server.
|
||||
// This is done in order to facilitate queries based on different conference configurations.
|
||||
// e.g. Find all RTCPeerConnections that connect to a specific shard or were created in a
|
||||
// conference with a specific version.
|
||||
let displayName = jitsiLocalStorage.getItem('callStatsUserName');
|
||||
|
||||
if (options.statisticsId || options.statisticsDisplayName) {
|
||||
if (options.statisticsId && options.statisticsDisplayName) {
|
||||
displayName = `${options.statisticsDisplayName} (${options.statisticsId})`;
|
||||
} else {
|
||||
displayName = options.statisticsId || options.statisticsDisplayName;
|
||||
}
|
||||
}
|
||||
|
||||
RTCStats.sendIdentityData({
|
||||
...getAmplitudeIdentity(),
|
||||
...options,
|
||||
endpointId: localParticipant?.id,
|
||||
confName: getAnalyticsRoomName(state, dispatch),
|
||||
displayName,
|
||||
...identity
|
||||
});
|
||||
} catch (error) {
|
||||
// If the connection failed do not impact jitsi-meet just silently fail.
|
||||
logger.error('RTCStats connect failed with: ', error);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the faceLandmarks data can be sent to the rtcstats server.
|
||||
*
|
||||
* @param {IStateful} stateful - The redux store or {@code getState} function.
|
||||
* @returns {boolean}
|
||||
*/
|
||||
export function canSendFaceLandmarksRtcstatsData(stateful: IStateful): boolean {
|
||||
export function canSendFaceLandmarksRTCStatsData(stateful: IStateful): boolean {
|
||||
const state = toState(stateful);
|
||||
const { faceLandmarks } = state['features/base/config'];
|
||||
|
||||
if (faceLandmarks?.enableRTCStats && canSendRtcstatsData(state)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
return Boolean(faceLandmarks?.enableRTCStats && isRTCStatsEnabled(state));
|
||||
}
|
||||
|
||||
@@ -4,27 +4,19 @@ import { IStore } from '../app/types';
|
||||
import {
|
||||
CONFERENCE_JOINED,
|
||||
CONFERENCE_TIMESTAMP_CHANGED,
|
||||
CONFERENCE_UNIQUE_ID_SET,
|
||||
CONFERENCE_WILL_LEAVE,
|
||||
E2E_RTT_CHANGED
|
||||
} from '../base/conference/actionTypes';
|
||||
import { LIB_WILL_INIT } from '../base/lib-jitsi-meet/actionTypes';
|
||||
import { DOMINANT_SPEAKER_CHANGED } from '../base/participants/actionTypes';
|
||||
import MiddlewareRegistry from '../base/redux/MiddlewareRegistry';
|
||||
import { TRACK_ADDED, TRACK_UPDATED } from '../base/tracks/actionTypes';
|
||||
import { getCurrentRoomId, isInBreakoutRoom } from '../breakout-rooms/functions';
|
||||
import { extractFqnFromPath } from '../dynamic-branding/functions.any';
|
||||
import { ADD_FACE_LANDMARKS } from '../face-landmarks/actionTypes';
|
||||
import { FaceLandmarks } from '../face-landmarks/types';
|
||||
|
||||
import RTCStats from './RTCStats';
|
||||
import {
|
||||
canSendFaceLandmarksRtcstatsData,
|
||||
canSendRtcstatsData,
|
||||
connectAndSendIdentity,
|
||||
isRtcstatsEnabled
|
||||
canSendFaceLandmarksRTCStatsData,
|
||||
isRTCStatsEnabled
|
||||
} from './functions';
|
||||
import logger from './logger';
|
||||
|
||||
/**
|
||||
* Middleware which intercepts lib-jitsi-meet initialization and conference join in order init the
|
||||
@@ -36,77 +28,16 @@ import logger from './logger';
|
||||
MiddlewareRegistry.register((store: IStore) => (next: Function) => (action: AnyAction) => {
|
||||
const { getState } = store;
|
||||
const state = getState();
|
||||
const config = state['features/base/config'];
|
||||
const { analytics } = config;
|
||||
|
||||
switch (action.type) {
|
||||
case LIB_WILL_INIT: {
|
||||
if (isRtcstatsEnabled(state)) {
|
||||
// RTCStats "proxies" WebRTC functions such as GUM and RTCPeerConnection by rewriting the global
|
||||
// window functions. Because lib-jitsi-meet uses references to those functions that are taken on
|
||||
// init, we need to add these proxies before it initializes, otherwise lib-jitsi-meet will use the
|
||||
// original non proxy versions of these functions.
|
||||
try {
|
||||
// Default poll interval is 10000ms and standard stats will be used, if not provided in the config.
|
||||
const pollInterval = analytics?.rtcstatsPollInterval || 10000;
|
||||
const useLegacy = analytics?.rtcstatsUseLegacy || false;
|
||||
const sendSdp = analytics?.rtcstatsSendSdp || false;
|
||||
|
||||
// Initialize but don't connect to the rtcstats server wss, as it will start sending data for all
|
||||
// media calls made even before the conference started.
|
||||
|
||||
RTCStats.maybeInit({
|
||||
endpoint: analytics?.rtcstatsEndpoint,
|
||||
meetingFqn: extractFqnFromPath(state),
|
||||
useLegacy,
|
||||
pollInterval,
|
||||
sendSdp
|
||||
});
|
||||
} catch (error) {
|
||||
logger.error('Failed to initialize RTCStats: ', error);
|
||||
}
|
||||
} else {
|
||||
RTCStats.reset();
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
// Used for connecting to rtcstats server when joining a breakout room.
|
||||
// Breakout rooms do not have a meetingUniqueId.
|
||||
case CONFERENCE_JOINED: {
|
||||
if (isInBreakoutRoom(getState())) {
|
||||
connectAndSendIdentity(
|
||||
store,
|
||||
{
|
||||
isBreakoutRoom: true,
|
||||
roomId: getCurrentRoomId(getState())
|
||||
}
|
||||
);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
// Used for connecting to rtcstats server when joining the main room.
|
||||
// Using this event to be sure the meetingUniqueId can be retrieved.
|
||||
case CONFERENCE_UNIQUE_ID_SET: {
|
||||
if (!isInBreakoutRoom(getState())) {
|
||||
// Unique identifier for a conference session, not to be confused with meeting name
|
||||
// i.e. If all participants leave a meeting it will have a different value on the next join.
|
||||
const { conference } = action;
|
||||
const meetingUniqueId = conference?.getMeetingUniqueId();
|
||||
|
||||
connectAndSendIdentity(
|
||||
store,
|
||||
{
|
||||
isBreakoutRoom: false,
|
||||
meetingUniqueId
|
||||
}
|
||||
);
|
||||
if (isRTCStatsEnabled(state)) {
|
||||
RTCStats.init();
|
||||
}
|
||||
break;
|
||||
}
|
||||
case TRACK_ADDED: {
|
||||
if (canSendRtcstatsData(state)) {
|
||||
if (isRTCStatsEnabled(state)) {
|
||||
const jitsiTrack = action?.track?.jitsiTrack;
|
||||
const { ssrc, videoType } = jitsiTrack || { };
|
||||
|
||||
@@ -121,7 +52,7 @@ MiddlewareRegistry.register((store: IStore) => (next: Function) => (action: AnyA
|
||||
break;
|
||||
}
|
||||
case TRACK_UPDATED: {
|
||||
if (canSendRtcstatsData(state)) {
|
||||
if (isRTCStatsEnabled(state)) {
|
||||
const { videoType, jitsiTrack, muted } = action?.track || { };
|
||||
const { ssrc, isLocal, videoType: trackVideoType, conference } = jitsiTrack || { };
|
||||
|
||||
@@ -146,7 +77,7 @@ MiddlewareRegistry.register((store: IStore) => (next: Function) => (action: AnyA
|
||||
break;
|
||||
}
|
||||
case DOMINANT_SPEAKER_CHANGED: {
|
||||
if (canSendRtcstatsData(state)) {
|
||||
if (isRTCStatsEnabled(state)) {
|
||||
const { id, previousSpeakers, silence } = action.participant;
|
||||
|
||||
RTCStats.sendDominantSpeakerData({
|
||||
@@ -157,10 +88,10 @@ MiddlewareRegistry.register((store: IStore) => (next: Function) => (action: AnyA
|
||||
break;
|
||||
}
|
||||
case E2E_RTT_CHANGED: {
|
||||
if (canSendRtcstatsData(state)) {
|
||||
if (isRTCStatsEnabled(state)) {
|
||||
const { participant, rtt } = action.e2eRtt;
|
||||
|
||||
RTCStats.sendE2eRttData({
|
||||
RTCStats.sendE2ERTTData({
|
||||
remoteEndpointId: participant.getId(),
|
||||
rtt,
|
||||
remoteRegion: participant.getProperty('region')
|
||||
@@ -169,7 +100,7 @@ MiddlewareRegistry.register((store: IStore) => (next: Function) => (action: AnyA
|
||||
break;
|
||||
}
|
||||
case ADD_FACE_LANDMARKS: {
|
||||
if (canSendFaceLandmarksRtcstatsData(state)) {
|
||||
if (canSendFaceLandmarksRTCStatsData(state)) {
|
||||
const { duration, faceExpression, timestamp } = action.faceLandmarks as FaceLandmarks;
|
||||
const durationSeconds = Math.round(duration / 1000);
|
||||
|
||||
@@ -182,19 +113,13 @@ MiddlewareRegistry.register((store: IStore) => (next: Function) => (action: AnyA
|
||||
break;
|
||||
}
|
||||
case CONFERENCE_TIMESTAMP_CHANGED: {
|
||||
if (canSendRtcstatsData(state)) {
|
||||
if (isRTCStatsEnabled(state)) {
|
||||
const { conferenceTimestamp } = action;
|
||||
|
||||
RTCStats.sendConferenceTimestamp(conferenceTimestamp);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case CONFERENCE_WILL_LEAVE: {
|
||||
if (canSendRtcstatsData(state)) {
|
||||
RTCStats.close();
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return next(action);
|
||||
|
||||
@@ -1,11 +1,3 @@
|
||||
export type InitOptions = {
|
||||
endpoint?: string;
|
||||
meetingFqn: string;
|
||||
pollInterval: number;
|
||||
sendSdp: boolean;
|
||||
useLegacy: boolean;
|
||||
};
|
||||
|
||||
export type VideoTypeData = {
|
||||
ssrc: number;
|
||||
videoType: string;
|
||||
|
||||
@@ -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)));
|
||||
}
|
||||
}
|
||||
}));
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user