Compare commits

...

63 Commits

Author SHA1 Message Date
Gabriel Borlea
54d052de73 chore: update js-utils with new ua-parser (#13877)
* chore: update js-utils with new ua-parser

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

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

Fixes: https://github.com/jitsi/jitsi-meet/issues/13800
2023-09-23 22:01:28 +02:00
damencho
f0cb33a627 fix: Fixes missing import in wait for host module. 2023-09-21 14:45:12 -05:00
damencho
b5b7019325 fix: Adds check for missing main_room. 2023-09-21 12:17:53 -05:00
damencho
7ccd68eb18 feat: Introduces passing state to the token authUrl.
Fixes jitsi/jitsi-meet-electron#902.
2023-09-21 12:17:53 -05:00
damencho
44b0ac57eb feat: Updates base64-js dependency. 2023-09-21 12:17:53 -05:00
damencho
a411b7c969 fix: Fixes check for health check room. 2023-09-21 12:17:53 -05:00
damencho
fc8ce532f6 feat: Hides any error from the UI for the DialIn info app.
If the conference mapper return an error we show it on deeplinking page. In case the conf mapper receives non authenticated request it may return an error and this is normal so hide it from that page.
2023-09-21 12:17:53 -05:00
damencho
ef56b3c5b6 feat: Adds an event for host arrived. 2023-09-21 12:17:53 -05:00
Saúl Ibarra Corretgé
37e13804a5 fix(ios) fix compilation with Xcode 14.3
Fixes: https://github.com/jitsi/jitsi-meet/issues/13274
2023-09-21 19:17:15 +02:00
Saúl Ibarra Corretgé
8b209b3c6e fix(external-api) add policy to support the Compute Pressure API
https://w3c.github.io/compute-pressure/#policy-control
2023-09-21 17:15:36 +02:00
Mihaela Dumitru
cb26042d08 fix(virtual-background) display current settings (#13857) 2023-09-21 15:45:17 +03:00
Javier
2952d1cde8 Fix disable virtual background feature, now hides the feature everywhere 2023-09-20 09:05:10 -05:00
Licaon_Kter
8a7f456560 feat(android) add Fastlane metadata
Fix https://github.com/jitsi/jitsi-meet/issues/11786
2023-09-20 11:46:56 +02:00
Thomas Egebrand Gram
f74b6cd82f fix (mobile-layout) change "vh" to "dvh" for all layouts (#13840)
* Convert all vh units to dvh; fixing layout for mobile browsers such as Chrome for Android.
2023-09-20 12:19:54 +03:00
Calinteodor
d04515c35a feat(prejoin/native): fixed screen header hooks warning (#13845)
* feat(prejoin/native): fixed screen header hooks warning
2023-09-20 11:40:28 +03:00
Jaya Allamsetty
2aca0ce110 chore(deps) lib-jitsi-meet@latest
https://github.com/jitsi/lib-jitsi-meet/compare/v1689.0.0+0d5c73d1...v1691.0.0+255d8f49
2023-09-19 18:24:02 -04:00
Calinteodor
d0e49b27a1 feat(app/native): rework appNavigate so callkit video button does not end the call (#13814)
* feat(app/native): rework appNavigate so callkit video button does not end the call
2023-09-19 20:47:29 +03:00
Jaya Allamsetty
d97c365aed chore(deps) lib-jitsi-meet@latest
https://github.com/jitsi/lib-jitsi-meet/compare/v1687.0.0+cafe30d7...v1689.0.0+0d5c73d1
2023-09-19 12:54:18 -04:00
damencho
8304e77a04 chore(deps) lib-jitsi-meet@latest
https://github.com/jitsi/lib-jitsi-meet/compare/v1686.0.0+1b5830f1...v1687.0.0+cafe30d7
2023-09-14 16:02:29 +02:00
Saúl Ibarra Corretgé
b1db315582 fix(authentication) fix moderator logout 2023-09-14 13:44:08 +02:00
Shawn
4e785dd982 fix(config): missing colon in example for lobby config 2023-09-14 12:19:35 +02:00
damencho
40f5afcf43 chore(deps) lib-jitsi-meet@latest
https://github.com/jitsi/lib-jitsi-meet/compare/v1685.0.0+02c54a23...v1686.0.0+1b5830f1
2023-09-13 16:27:06 -05:00
Saúl Ibarra Corretgé
de2688bb33 chore(deps) lib-jitsi-meet@latest
https://github.com/jitsi/lib-jitsi-meet/compare/v1683.0.0+fc7775bc...v1685.0.0+02c54a23
2023-09-13 00:10:08 +02:00
Saúl Ibarra Corretgé
76db09303b fix(rn,conference) avoid starting to knock twice
The auto-knock process is started by the lobby middleware.
2023-09-12 23:20:41 +02:00
José Luís Andrade
ea4e20f9a7 lang: Update Portuguese translation (#13818) 2023-09-11 10:34:03 -05:00
rca
01a74856a3 Fix missing closing tag for identity node in presence stanza 2023-09-11 10:03:09 -05:00
Mihaela Dumitru
36045100bf feat(branding) add support for custom icons 2023-09-11 09:11:06 +02:00
Hristo Terezov
cc344cb548 chore(LJM): Update 2023-09-08 16:01:28 -05:00
Hristo Terezov
a2624952a0 feat(iframe-api): replace ice servers 2023-09-08 16:01:28 -05:00
Calinteodor
b8259e00dc sdk(react-native-sdk): created script that compares dep with peerDep versions (#13789)
sdk(react-native-sdk): created script that compares app dep with RNSDK peerDep versions
2023-09-08 20:26:25 +03:00
Horatiu Muresan
463c823d3b fix(toolbar-buttons) Show tileview in toolbar when separate reactions button (#13810) 2023-09-08 14:44:32 +03:00
Shawn
5a6f3ead5a feat(external-api) Add functions to query supported commands/events 2023-09-08 09:31:23 +02:00
Andrei Gavrilescu
1b4d666af3 chore(deps) lib-jitsi-meet@latest (#13801)
https://github.com/jitsi/lib-jitsi-meet/compare/v1681.0.0+6cd397fa...v1682.0.0+9832ef2c
2023-09-06 16:51:44 +03:00
Andrei Gavrilescu
77d299338a feat(rtcstats): use rtcstats from lib-jitsi-meet (#13693)
* added option to use rtctstats from lib-jitsi-meet

* Addressed review feedback:
- moved rtcstats function into JitsiMeetJS.rtcstats
- changed from callbacks to events

* moved rtcstatsUseLibJitsi from analytics to testing

* fixed linting errors

* use ljm rtcstats

* remove debug logs, additional dependencies

* fix ts and dependency

* address code review

---------

Co-authored-by: Nils Ohlmeier <github@ohlmeier.org>
2023-09-06 16:00:53 +03:00
Horatiu Muresan
33fc6e2f3f fix(disable-filmstrip) Fix disabling filmstrim through config
- there was a problem with pinning the participants from the Participants pane with the previous approach
2023-09-06 14:30:13 +03:00
Calinteodor
a95eaa6c2e feat(base/ui): Native buttons UI fixes (#13788)
* feat(base/ui): native buttons UI fixes and improvements
2023-09-05 16:36:09 +03:00
Andrei Gavrilescu
5a3947bb23 feat(amplitude) add amplitude UTM tracking option 2023-09-05 13:20:31 +02:00
Calinteodor
f84a561d9e sdk(react-native-sdk): Update rnsdk peer deps (#13793)
* sdk(react-native-sdk): prepare_sdk script updates regarding deps and peer deps
2023-09-05 14:09:13 +03:00
Robert Pintilii
295878ffff ref(styles) Move some SCSS to JSS (#13568) 2023-09-05 11:20:01 +03:00
Saúl Ibarra Corretgé
609942654a fix(android) disable full-screen when screen-sharing
Fixes not being able to put the app in background mode easily on Android
13.

Fixes: https://github.com/jitsi/jitsi-meet/issues/13513
2023-09-04 19:21:20 +02:00
Calin-Teodor
60ad0196c3 ref(dependency): latest react-native-video and device-info updates 2023-09-04 19:30:12 +03:00
Calin-Teodor
caea6966ef ref(dependency): reverted react-native-dialog dep update 2023-09-04 19:30:12 +03:00
Calin-Teodor
d4c269f7cb ref(dependency): updated native fixDeviceID 2023-09-04 19:30:12 +03:00
Calin-Teodor
54a1ee53b4 ref(dependency): reverted react-native-dialog dep updates 2023-09-04 19:30:12 +03:00
Calin-Teodor
2c51e8ac06 ref(dependency): regenerated podfile.lock file 2023-09-04 19:30:12 +03:00
Calin-Teodor
3cbd69eef2 ref(dependency): update deps after rebase 2023-09-04 19:30:12 +03:00
Calin-Teodor
ee539644d8 ref(dependency): replaced DeviceInfo.getUniqueId with getUniqueId 2023-09-04 19:30:12 +03:00
Calin-Teodor
465263bc97 fixed linter 2023-09-04 19:30:12 +03:00
Calin-Teodor
1def65eb90 ref(dependency): update react native device info dependency 2023-09-04 19:30:12 +03:00
Calin-Teodor
746be98bfc ref(dependency): update react native gesture handler dependency 2023-09-04 19:30:12 +03:00
Calin-Teodor
99b58dd318 ref(dependency): fixed rebase conflict 2023-09-04 19:30:12 +03:00
Gabriel Borlea
df3ef0d895 fix(video-select): remove video preview from device selection and fix video switch on mobile browsers (#13780)
* fix(video-select): remove video preview from device selection and fix video switch on android browsers

* simplify if statement

* add for all mobile devices the stop stream

* move mobile check to middleware

* code review
2023-09-04 16:27:04 +03:00
Saúl Ibarra Corretgé
83e4042668 fix(android) bump target API level to 33
It is now required by the Play Store to target an API released within a
year of $NOW to be able to push updates.
2023-09-04 12:47:08 +02:00
Saúl Ibarra Corretgé
c6e87568b6 chore(deps) react-antive-webview@13.5.1
Requirement for bumping Android API target to 33.
2023-09-04 12:47:08 +02:00
Дамян Минков
0170c65c7b feat: Sends conference request over http before connecting to xmpp (#13725)
* feat: Moves redirected event to connection events.

* feat: Pass room name when connecting.

We need the room name we will join to be able to send the http conference request from ljm.

* squash: Drops dispatching redirected action.

* squash: Updates ljm.
2023-08-29 14:13:04 -05:00
114 changed files with 4219 additions and 2519 deletions

View File

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

View File

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

View File

@@ -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.

View File

@@ -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

View File

@@ -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
*/
/**

View File

@@ -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;
}
}
}
}

View File

@@ -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;

View File

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

View File

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

View File

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

View File

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

View File

@@ -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';

View File

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

View File

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

View File

@@ -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

View File

@@ -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",

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 302 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 168 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 243 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 928 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 534 KiB

View File

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

View File

@@ -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

File diff suppressed because it is too large Load Diff

View File

@@ -23,7 +23,7 @@
"@giphy/react-components": "6.8.1",
"@giphy/react-native-sdk": "2.3.0",
"@jitsi/excalidraw": "https://github.com/jitsi/excalidraw/releases/download/v0.0.14/jitsi-excalidraw-0.0.14.tgz",
"@jitsi/js-utils": "2.1.3",
"@jitsi/js-utils": "2.2.0",
"@jitsi/logger": "2.0.2",
"@jitsi/rnnoise-wasm": "0.1.0",
"@jitsi/rtcstats": "9.5.1",
@@ -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",

File diff suppressed because it is too large Load Diff

View File

@@ -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"
]
}
}

View File

@@ -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);

View File

@@ -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;
}

View File

@@ -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 => {

View File

@@ -11,6 +11,7 @@ export interface IEvent {
interface IOptions {
amplitudeAPPKey?: string;
amplitudeIncludeUTM?: boolean;
blackListedEvents?: string[];
envType?: string;
googleAnalyticsTrackingId?: string;

View File

@@ -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
};

View File

@@ -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);
}
}

View File

@@ -1,4 +1,5 @@
import { setRoom } from '../base/conference/actions';
import { getConferenceState } from '../base/conference/functions';
import {
configWillLoad,
loadConfigError,
@@ -10,6 +11,7 @@ import {
restoreConfig
} from '../base/config/functions.native';
import { connect, disconnect, setLocationURL } from '../base/connection/actions.native';
import { JITSI_CONNECTION_URL_KEY } from '../base/connection/constants';
import { loadConfig } from '../base/lib-jitsi-meet/functions.native';
import { createDesiredLocalTracks } from '../base/tracks/actions.native';
import isInsecureRoomName from '../base/util/isInsecureRoomName';
@@ -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());

View File

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

View File

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

View File

@@ -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';

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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([]);
}
});
};
}

View File

@@ -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) {

View File

@@ -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

View File

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

View File

@@ -182,6 +182,7 @@ export interface IConfig {
_screenshotHistoryRegionUrl?: number;
analytics?: {
amplitudeAPPKey?: string;
amplitudeIncludeUTM?: boolean;
blackListedEvents?: string[];
disabled?: boolean;
googleAnalyticsTrackingId?: string;

View File

@@ -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
});
});
};

View File

@@ -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;

View 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>;
}

View File

@@ -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())) {

View File

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

View File

@@ -18,3 +18,4 @@ export function isMobileBrowser() {
export function isIosMobileBrowser() {
return Platform.OS === 'ios';
}

View 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;

View 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;

View 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;

View 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
};

View File

@@ -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
};

View File

@@ -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 './_';

View File

@@ -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;

View File

@@ -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());
}
/**

View File

@@ -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>
);
}

View File

@@ -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>
);
};

View File

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

View File

@@ -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`]

View File

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

View File

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

View File

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

View File

@@ -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)

View File

@@ -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,

View File

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

View File

@@ -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

View File

@@ -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'];

View File

@@ -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;
};

View 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);
});

View File

@@ -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
};

View File

@@ -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) {

View File

@@ -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,

View File

@@ -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,

View File

@@ -116,6 +116,6 @@ export default {
},
sendBtn: {
marginRight: BaseTheme.spacing[2]
marginRight: BaseTheme.spacing[3]
}
};

View File

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

View File

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

View File

@@ -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;
}

View File

@@ -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));
}
}

View File

@@ -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 } />
)}
</>
);

View File

@@ -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: {

View File

@@ -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>
);
};

View File

@@ -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,

View File

@@ -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();

View File

@@ -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();

View File

@@ -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));
}

View File

@@ -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);

View File

@@ -1,11 +1,3 @@
export type InitOptions = {
endpoint?: string;
meetingFqn: string;
pollInterval: number;
sendSdp: boolean;
useLegacy: boolean;
};
export type VideoTypeData = {
ssrc: number;
videoType: string;

View File

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

View File

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

View File

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

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